mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-05-19 09:40:22 -07:00
Compare commits
450 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6dfaec03e8 | ||
|
5fbc4fee64 | ||
|
004370bd16 | ||
|
de4baa1002 | ||
|
163ac157d3 | ||
|
e2362d4d51 | ||
|
d6b59feff8 | ||
|
94305125ef | ||
|
79cbe89deb | ||
|
bf63fe8f25 | ||
|
8bd5950296 | ||
|
6e0539ab91 | ||
|
4649aa9700 | ||
|
c009652e77 | ||
|
b9f7a9ba2b | ||
|
a1960877cf | ||
|
bb0925af91 | ||
|
be117dbafa | ||
|
06dc13ad2d | ||
|
c6c2e69b8f | ||
|
e67c868ddd | ||
|
d33f2e2f70 | ||
|
082edafffa | ||
|
7c8dc332b3 | ||
|
ea961915b5 | ||
|
7943bdfe82 | ||
|
312a7884fc | ||
|
ac02f54c89 | ||
|
24b337b940 | ||
|
a5083f99ce | ||
|
f89cdba5df | ||
|
f7b677d136 | ||
|
3f68a8f3d7 | ||
|
9d738ad0c0 | ||
|
6c5108ed17 | ||
|
e0f1000df6 | ||
|
ea99421ec8 | ||
|
af8c386d5e | ||
|
71d71d2d98 | ||
|
c9ebcbd8ab | ||
|
dec0dc3196 | ||
|
2f0a269f07 | ||
|
0a0893a765 | ||
|
35160a1cdb | ||
|
f1d23c06e3 | ||
|
22b677900f | ||
|
bb6f0f5519 | ||
|
b6ef99ee55 | ||
|
bb8601b2ba | ||
|
02b47b7469 | ||
|
d922b7ac11 | ||
|
2acf25c689 | ||
|
80007698d3 | ||
|
3ad0e83471 | ||
|
eca13f08a2 | ||
|
4f99f82b19 | ||
|
327d74f161 | ||
|
9da0995df4 | ||
|
e9abbc1a02 | ||
|
9bd30e8e48 | ||
|
59212d08d3 | ||
|
6ebebb2aaa | ||
|
e92e2ef813 | ||
|
4a30819302 | ||
|
9b42af96f0 | ||
|
648a65f197 | ||
|
bdf01f46a6 | ||
|
1c775f3a82 | ||
|
e50df40a19 | ||
|
1fa76d2a42 | ||
|
44aa5a417d | ||
|
2c3897585d | ||
|
6e9141a9ca | ||
|
c8e4a84519 | ||
|
f02a50a69d | ||
|
b9c774937f | ||
|
67dd809a80 | ||
|
e0a85678e1 | ||
|
23af5fb043 | ||
|
5dec4b8e37 | ||
|
827082a33a | ||
|
6c2a550e1e | ||
|
8e8fc9c503 | ||
|
2057023dc5 | ||
|
3f2fe0afee | ||
|
56c7ad175a | ||
|
5b7a30846f | ||
|
2a4dba3fbf | ||
|
84d65865e6 | ||
|
d9aaa11873 | ||
|
67ad9917ad | ||
|
daa157b5f9 | ||
|
ca5e294ad6 | ||
|
6c7947b819 | ||
|
9acb4a5405 | ||
|
0096c74c11 | ||
|
8c48355b03 | ||
|
f9b86de963 | ||
|
d23b74975a | ||
|
a5cbdb3dfe | ||
|
b6bac8484e | ||
|
805fa32d18 | ||
|
2d518dd1f9 | ||
|
8575d26179 | ||
|
2e81a7adfe | ||
|
cd5440fb62 | ||
|
2ee690e87a | ||
|
59f86a45d3 | ||
|
2d31af38a2 | ||
|
0da1176e7d | ||
|
eeffcd50b7 | ||
|
625743d7c8 | ||
|
3d0171040a | ||
|
93429d0f85 | ||
|
9c4b0baf10 | ||
|
179487aaed | ||
|
b407d62b63 | ||
|
9bd1e737bc | ||
|
c12231c621 | ||
|
b0df573834 | ||
|
85b2ceecd1 | ||
|
fee7ac79f1 | ||
|
54d5540c10 | ||
|
d0251c77fe | ||
|
6aa5993d4b | ||
|
6f78d211bf | ||
|
51aa339830 | ||
|
381c521d02 | ||
|
57495db10e | ||
|
47e37175ca | ||
|
8697946718 | ||
|
8058859701 | ||
|
e9ff90c8ff | ||
|
bf9f74ea5b | ||
|
9b5091b895 | ||
|
a4f165e3ab | ||
|
d1def67000 | ||
|
56af4d4a74 | ||
|
b0f6645408 | ||
|
3dbe371fe4 | ||
|
30d06b3b4c | ||
|
6a055d922c | ||
|
e007523229 | ||
|
88353c80da | ||
|
cd3bcce42d | ||
|
1ea3552f2d | ||
|
9ed7565fcb | ||
|
7bb9f35d2d | ||
|
b138d5740a | ||
|
3f0c8c2900 | ||
|
0e6e9417f1 | ||
|
fded2a5fe1 | ||
|
e14eeb288f | ||
|
1cbcefddc9 | ||
|
4fec9ffca8 | ||
|
00225a035b | ||
|
286de9564e | ||
|
038524a580 | ||
|
8f9557d183 | ||
|
58e7d2ea63 | ||
|
b7df9f8caa | ||
|
ebb986e767 | ||
|
a2907db2de | ||
|
470ad1d072 | ||
|
6d7550d58e | ||
|
af55fc2b38 | ||
|
3d2f49f6fe | ||
|
50b2472438 | ||
|
ae2a09915f | ||
|
9c84575229 | ||
|
cddb5f57f8 | ||
|
5dc424d302 | ||
|
040d8f2171 | ||
|
c81caa673b | ||
|
082245dadb | ||
|
c33f623719 | ||
|
824778c009 | ||
|
922bad2b92 | ||
|
538ba956dc | ||
|
443c057042 | ||
|
5b88515faf | ||
|
92c81b1225 | ||
|
53679e4c43 | ||
|
8b766a2522 | ||
|
c21302b409 | ||
|
8a5b81716a | ||
|
7099e174ac | ||
|
dd810779d4 | ||
|
5011f6e9f1 | ||
|
a2799ccb41 | ||
|
a13b5e0196 | ||
|
9626f16757 | ||
|
f7ff34fdf9 | ||
|
b9de003f81 | ||
|
1659fb9b43 | ||
|
dd1bc5b898 | ||
|
c9bfbe1e3d | ||
|
88524a2b52 | ||
|
9c6732bd26 | ||
|
392bb0944a | ||
|
90b849912f | ||
|
6d17b3ed68 | ||
|
f16ea0812d | ||
|
be9e308999 | ||
|
d53b7310ee | ||
|
e30bbb8cff | ||
|
7f45640401 | ||
|
0951820f63 | ||
|
c3e85f2b44 | ||
|
3ad7a0d95e | ||
|
82d3183a04 | ||
|
798f8981eb | ||
|
96f01b92a0 | ||
|
abfa65c2c1 | ||
|
f608d4d9b3 | ||
|
23e21133ba | ||
|
09905560ff | ||
|
25a7145c79 | ||
|
19a08bee8a | ||
|
1a50324013 | ||
|
86ef683308 | ||
|
d938e955af | ||
|
cad1f5fae2 | ||
|
2198bd92fa | ||
|
a4387ed491 | ||
|
d2a409f89f | ||
|
6cdb99ea61 | ||
|
551ad3bada | ||
|
8856f72df5 | ||
|
d596f6ebd0 | ||
|
6cd9479634 | ||
|
3bfa125b2e | ||
|
51765f2f4c | ||
|
67abd49678 | ||
|
a7fe296772 | ||
|
f75991538b | ||
|
962d47e6a1 | ||
|
19b6a45abb | ||
|
c51790b56d | ||
|
2af3734e0c | ||
|
61733f6378 | ||
|
7227e94ce5 | ||
|
341a19e0d0 | ||
|
fed4fea217 | ||
|
053a1669bb | ||
|
31d3f16254 | ||
|
304a60e8e9 | ||
|
1d35859861 | ||
|
601e122e9f | ||
|
efb2e8ce1e | ||
|
8d464e5c78 | ||
|
d67809d6c4 | ||
|
6abb962f0d | ||
|
6d95c130d5 | ||
|
4782ebd5e0 | ||
|
4993d29a16 | ||
|
23adbd6795 | ||
|
9df8ab42b1 | ||
|
cb7501ff11 | ||
|
3b66f37a31 | ||
|
3eccb7c363 | ||
|
f30a30867e | ||
|
7313dca472 | ||
|
99bf2b01dc | ||
|
ee1360cc07 | ||
|
db6bb21a62 | ||
|
da7c81fb96 | ||
|
a4e3d56de1 | ||
|
7c83b90f95 | ||
|
97b5b7769c | ||
|
2708f9e81d | ||
|
f3241fd657 | ||
|
cfe357188d | ||
|
792451e331 | ||
|
7dafd58a32 | ||
|
b92550b67b | ||
|
383d3b336b | ||
|
fc7e634395 | ||
|
c9584b035b | ||
|
f34fd5c4b6 | ||
|
d51c6c005a | ||
|
ea05881319 | ||
|
1d4e3df19c | ||
|
0f6181d309 | ||
|
e902e2fef4 | ||
|
07cbfee225 | ||
|
d675844510 | ||
|
54e609d657 | ||
|
43bbcca06f | ||
|
ad9bfdd981 | ||
|
36194c2742 | ||
|
0c1cbd99f3 | ||
|
96cfc0ed13 | ||
|
da8ecddce9 | ||
|
545a7dc759 | ||
|
16f783832e | ||
|
f4d07b9cbd | ||
|
0b6eccf4d3 | ||
|
3ac4541e9f | ||
|
7b72e982f2 | ||
|
a68db3ac02 | ||
|
b12905daca | ||
|
ca740d9ace | ||
|
e80c102dee | ||
|
8ac66a9e04 | ||
|
04dde9a4eb | ||
|
81341702af | ||
|
d34c5c88a7 | ||
|
4b8aa91ae5 | ||
|
a775b493fd | ||
|
a6dbff502f | ||
|
51480d57a6 | ||
|
d9bd261be8 | ||
|
9d62eb997a | ||
|
e028ea3792 | ||
|
1035f6b1ff | ||
|
a7f1276021 | ||
|
4fcb1b2202 | ||
|
949092fd22 | ||
|
4a7e7094ad | ||
|
fc0d9b90a9 | ||
|
335aa4937a | ||
|
803c447845 | ||
|
c5415adbe8 | ||
|
251376597f | ||
|
e593f5b7ee | ||
|
6b19be2477 | ||
|
041544853c | ||
|
a7ae9e4043 | ||
|
595e7845b8 | ||
|
44fb9fce2c | ||
|
339c46a6ed | ||
|
fe97c0a152 | ||
|
826f3fad5b | ||
|
bc55049327 | ||
|
d58e9353fc | ||
|
ca60fef4db | ||
|
a25307d6c8 | ||
|
b80947a8b3 | ||
|
ad793a0d8f | ||
|
120e55e7c7 | ||
|
3941a7701d | ||
|
96e130fbf9 | ||
|
180c4eaf8b | ||
|
81529288cf | ||
|
bcc7473a87 | ||
|
bc78c644db | ||
|
dc7267a0fb | ||
|
3224324e25 | ||
|
0f61f08eb1 | ||
|
a0e8dbe9df | ||
|
e95254a86f | ||
|
2f484d8ce5 | ||
|
364772ddd2 | ||
|
2e207833bc | ||
|
92b35a65f8 | ||
|
ac8fecbbf2 | ||
|
8596817374 | ||
|
28bff84a0a | ||
|
61101289fa | ||
|
13faa39b66 | ||
|
6b61271bbb | ||
|
1be86392e0 | ||
|
63058453fa | ||
|
7f23cd63a5 | ||
|
8905d54a9f | ||
|
25a4eaf5ae | ||
|
0000157917 | ||
|
65b1b0e38a | ||
|
c032cda4b7 | ||
|
eab044d829 | ||
|
55e62a4411 | ||
|
5b2f614aad | ||
|
4386b8e805 | ||
|
6b012d8129 | ||
|
a928ca4221 | ||
|
d1570defbf | ||
|
b732c23e36 | ||
|
49965703fa | ||
|
609838aebd | ||
|
515f120b5c | ||
|
a66315d232 | ||
|
bdf10ab7c0 | ||
|
a02678800b | ||
|
387df97d85 | ||
|
a9d97a1dda | ||
|
3bb71b0cb8 | ||
|
87b33c96c0 | ||
|
5e975c43f8 | ||
|
7efa2e46d3 | ||
|
db0b92b62d | ||
|
33b81cac48 | ||
|
6a13a4f64d | ||
|
b13d835d95 | ||
|
d53506b7f7 | ||
|
78a35d4d43 | ||
|
a933d0bc90 | ||
|
2cae30e399 | ||
|
8e57989cd2 | ||
|
b9f5835534 | ||
|
e70778e89d | ||
|
87c4a2b4b1 | ||
|
0aa31676e3 | ||
|
9f0e88bcb1 | ||
|
eb4b389846 | ||
|
dc337bab0a | ||
|
2cfb338530 | ||
|
48646e3451 | ||
|
985394a19e | ||
|
ec36f8c3ff | ||
|
a726d03641 | ||
|
91afd4214a | ||
|
4dc6c73c5a | ||
|
36d03b4101 | ||
|
d161acb0a3 | ||
|
30ee6f08ee | ||
|
ced5b92aa9 | ||
|
191315a2ea | ||
|
5370064f00 | ||
|
b6189c659e | ||
|
0b36942f68 | ||
|
7e05cde008 | ||
|
418d048b27 | ||
|
009dda1488 | ||
|
ba535fb5a3 | ||
|
427aaeeb2e | ||
|
f5cff746bc | ||
|
457f53b7ee | ||
|
eb35f7978e | ||
|
fc69bd366c | ||
|
9b01a8f9ae | ||
|
0ff5dd2360 | ||
|
3c7819301b | ||
|
699e651db2 | ||
|
9eddb71b8e | ||
|
abf115228e | ||
|
fdfc418be5 | ||
|
5bf74362b9 | ||
|
431ea38620 | ||
|
caba5c4348 | ||
|
07f97d42cf | ||
|
e33d6e73f5 | ||
|
478da4f271 | ||
|
7ce66f73cf | ||
|
bc76a30c23 | ||
|
5e81c60b35 | ||
|
b3e5ae9d28 | ||
|
a024f14fdd | ||
|
8c30c8294a | ||
|
c44d263419 |
@ -6,3 +6,16 @@
|
|||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
[target.i686-pc-windows-msvc]
|
[target.i686-pc-windows-msvc]
|
||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
|
# Do the same for MUSL targets. At the time of writing (2023-10-23), this is
|
||||||
|
# the default. But the plan is for the default to change to dynamic linking.
|
||||||
|
# The whole point of MUSL with respect to ripgrep is to create a fully
|
||||||
|
# statically linked executable.
|
||||||
|
#
|
||||||
|
# See: https://github.com/rust-lang/compiler-team/issues/422
|
||||||
|
# See: https://github.com/rust-lang/compiler-team/issues/422#issuecomment-812135847
|
||||||
|
[target.x86_64-unknown-linux-musl]
|
||||||
|
rustflags = [
|
||||||
|
"-C", "target-feature=+crt-static",
|
||||||
|
"-C", "link-self-contained=yes",
|
||||||
|
]
|
||||||
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: [BurntSushi]
|
55
.github/ISSUE_TEMPLATE/bug_report.md
vendored
55
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: An issue with ripgrep or any of its crates (ignore, globset, etc.)
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
#### What version of ripgrep are you using?
|
|
||||||
|
|
||||||
Replace this text with the output of `rg --version`.
|
|
||||||
|
|
||||||
#### How did you install ripgrep?
|
|
||||||
|
|
||||||
If you installed ripgrep with snap and are getting strange file permission or
|
|
||||||
file not found errors, then please do not file a bug. Instead, use one of the
|
|
||||||
Github binary releases.
|
|
||||||
|
|
||||||
#### What operating system are you using ripgrep on?
|
|
||||||
|
|
||||||
Replace this text with your operating system and version.
|
|
||||||
|
|
||||||
#### Describe your bug.
|
|
||||||
|
|
||||||
Give a high level description of the bug.
|
|
||||||
|
|
||||||
#### What are the steps to reproduce the behavior?
|
|
||||||
|
|
||||||
If possible, please include both your search patterns and the corpus on which
|
|
||||||
you are searching. Unless the bug is very obvious, then it is unlikely that it
|
|
||||||
will be fixed if the ripgrep maintainers cannot reproduce it.
|
|
||||||
|
|
||||||
If the corpus is too big and you cannot decrease its size, file the bug anyway
|
|
||||||
and the ripgrep maintainers will help figure out next steps.
|
|
||||||
|
|
||||||
#### What is the actual behavior?
|
|
||||||
|
|
||||||
Show the command you ran and the actual output. Include the `--debug` flag in
|
|
||||||
your invocation of ripgrep.
|
|
||||||
|
|
||||||
If the output is large, put it in a gist: https://gist.github.com/
|
|
||||||
|
|
||||||
If the output is small, put it in code fences:
|
|
||||||
|
|
||||||
```
|
|
||||||
your
|
|
||||||
output
|
|
||||||
goes
|
|
||||||
here
|
|
||||||
```
|
|
||||||
|
|
||||||
#### What is the expected behavior?
|
|
||||||
|
|
||||||
What do you think ripgrep should have done?
|
|
101
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
101
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: An issue with ripgrep or any of its crates (ignore, globset, etc.).
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Please review the following common issues before filing a bug. You may also be interested in reading the [FAQ](https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md)
|
||||||
|
and the [user guide](https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md).
|
||||||
|
|
||||||
|
* Unable to search for text with leading dash/hyphen: This is not a bug. Use `rg -- -mytext` or `rg -e -mytext`. See #102, #215, #624.
|
||||||
|
* Unable to build with old version of Rust. This is not a bug. ripgrep tracks the latest stable release of Rust. See #1019, #1433, #2534.
|
||||||
|
* ripgrep package is broken or out of date. ripgrep's author does not maintain packages for Red Hat, Ubuntu, Arch, Homebrew, WinGet, etc. If you have an issue with one of these, please contact your package maintainer. See #1637, #2264, #2459.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: issue-not-common
|
||||||
|
attributes:
|
||||||
|
label: Please tick this box to confirm you have reviewed the above.
|
||||||
|
options:
|
||||||
|
- label: I have a different issue.
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: ripgrep-version
|
||||||
|
attributes:
|
||||||
|
label: What version of ripgrep are you using?
|
||||||
|
description: Enter the output of `rg --version`.
|
||||||
|
placeholder: ex. ripgrep 0.2.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: install-method
|
||||||
|
attributes:
|
||||||
|
label: How did you install ripgrep?
|
||||||
|
description: |
|
||||||
|
If you installed ripgrep with snap and are getting strange file permission or file not found errors, then please do not file a bug. Instead, use one of the GitHub binary releases.
|
||||||
|
|
||||||
|
Please report any other issues with downstream ripgrep packages to their respective maintainers as mentioned above.
|
||||||
|
placeholder: ex. Cargo, APT, Homebrew
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: operating-system
|
||||||
|
attributes:
|
||||||
|
label: What operating system are you using ripgrep on?
|
||||||
|
description: Enter the name and version of your operating system.
|
||||||
|
placeholder: ex. Debian 12.0, macOS 13.4.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe your bug.
|
||||||
|
description: Give a high level description of the bug.
|
||||||
|
placeholder: ex. ripgrep fails to return the expected matches when...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: steps-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: What are the steps to reproduce the behavior?
|
||||||
|
description: |
|
||||||
|
If possible, please include both your search patterns and the corpus on which you are searching. Unless the bug is very obvious, then it is unlikely that it will be fixed if the ripgrep maintainers cannot reproduce it.
|
||||||
|
|
||||||
|
If the corpus is too big and you cannot decrease its size, file the bug anyway and the ripgrep maintainers will help figure out next steps.
|
||||||
|
placeholder: >
|
||||||
|
ex. Run `rg bar` in a directory containing a file with the lines 'bar' and 'barbaz'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual-behavior
|
||||||
|
attributes:
|
||||||
|
label: What is the actual behavior?
|
||||||
|
description: |
|
||||||
|
Show the command you ran and the actual output. **Include the `--debug` flag in your invocation of ripgrep.**
|
||||||
|
|
||||||
|
If the output is large, put it in a gist: <https://gist.github.com/>
|
||||||
|
|
||||||
|
If the output is small, put it in code fences (see placeholder text).
|
||||||
|
placeholder: |
|
||||||
|
ex.
|
||||||
|
```
|
||||||
|
$ rg --debug bar
|
||||||
|
DEBUG|grep_regex::literal|crates/regex/src/literal.rs:58: literal prefixes detected: Literals { lits: [Complete(bar)], limit_size: 250, limit_class: 10 }
|
||||||
|
...
|
||||||
|
```
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: What is the expected behavior?
|
||||||
|
description: What do you think ripgrep should have done?
|
||||||
|
placeholder: ex. ripgrep should have returned 2 matches
|
||||||
|
validations:
|
||||||
|
required: true
|
203
.github/workflows/ci.yml
vendored
203
.github/workflows/ci.yml
vendored
@ -6,6 +6,27 @@ on:
|
|||||||
- master
|
- master
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '00 01 * * *'
|
- cron: '00 01 * * *'
|
||||||
|
|
||||||
|
# The section is needed to drop write-all permissions that are granted on
|
||||||
|
# `schedule` event. By specifying any permission explicitly all others are set
|
||||||
|
# to none. By using the principle of least privilege the damage a compromised
|
||||||
|
# workflow can do (because of an injection or compromised third party tool or
|
||||||
|
# action) is restricted. Currently the worklow doesn't need any additional
|
||||||
|
# permission except for pulling the code. Adding labels to issues, commenting
|
||||||
|
# on pull-requests, etc. may need additional permissions:
|
||||||
|
#
|
||||||
|
# Syntax for this section:
|
||||||
|
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||||
|
#
|
||||||
|
# Reference for how to assign permissions on a job-by-job basis:
|
||||||
|
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
||||||
|
#
|
||||||
|
# Reference for available permissions that we can enable if needed:
|
||||||
|
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
||||||
|
permissions:
|
||||||
|
# to fetch code (actions/checkout)
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: test
|
name: test
|
||||||
@ -14,100 +35,101 @@ jobs:
|
|||||||
# systems.
|
# systems.
|
||||||
CARGO: cargo
|
CARGO: cargo
|
||||||
# When CARGO is set to CROSS, this is set to `--target matrix.target`.
|
# When CARGO is set to CROSS, this is set to `--target matrix.target`.
|
||||||
|
# Note that we only use cross on Linux, so setting a target on a
|
||||||
|
# different OS will just use normal cargo.
|
||||||
TARGET_FLAGS:
|
TARGET_FLAGS:
|
||||||
# When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
|
# When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
|
||||||
TARGET_DIR: ./target
|
TARGET_DIR: ./target
|
||||||
|
# Bump this as appropriate. We pin to a version to make sure CI
|
||||||
|
# continues to work as cross releases in the past have broken things
|
||||||
|
# in subtle ways.
|
||||||
|
CROSS_VERSION: v0.2.5
|
||||||
# Emit backtraces on panics.
|
# Emit backtraces on panics.
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
build:
|
|
||||||
# We test ripgrep on a pinned version of Rust, along with the moving
|
|
||||||
# targets of 'stable' and 'beta' for good measure.
|
|
||||||
- pinned
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
# Our release builds are generated by a nightly compiler to take
|
|
||||||
# advantage of the latest optimizations/compile time improvements. So
|
|
||||||
# we test all of them here. (We don't do mips releases, but test on
|
|
||||||
# mips for big-endian coverage.)
|
|
||||||
- nightly
|
|
||||||
- nightly-musl
|
|
||||||
- nightly-32
|
|
||||||
- nightly-mips
|
|
||||||
- nightly-arm
|
|
||||||
- macos
|
|
||||||
- win-msvc
|
|
||||||
- win-gnu
|
|
||||||
include:
|
include:
|
||||||
- build: pinned
|
- build: pinned
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: 1.52.1
|
rust: 1.74.0
|
||||||
- build: stable
|
- build: stable
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: stable
|
rust: stable
|
||||||
- build: beta
|
- build: beta
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: beta
|
rust: beta
|
||||||
- build: nightly
|
- build: nightly
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: nightly
|
|
||||||
- build: nightly-musl
|
|
||||||
os: ubuntu-18.04
|
|
||||||
rust: nightly
|
rust: nightly
|
||||||
|
- build: stable-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
target: x86_64-unknown-linux-musl
|
target: x86_64-unknown-linux-musl
|
||||||
- build: nightly-32
|
- build: stable-x86
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: nightly
|
rust: stable
|
||||||
target: i686-unknown-linux-gnu
|
target: i686-unknown-linux-gnu
|
||||||
- build: nightly-mips
|
- build: stable-aarch64
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: nightly
|
rust: stable
|
||||||
target: mips64-unknown-linux-gnuabi64
|
target: aarch64-unknown-linux-gnu
|
||||||
- build: nightly-arm
|
- build: stable-arm-gnueabihf
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: nightly
|
rust: stable
|
||||||
# For stripping release binaries:
|
target: armv7-unknown-linux-gnueabihf
|
||||||
# docker run --rm -v $PWD/target:/target:Z \
|
- build: stable-arm-musleabihf
|
||||||
# rustembedded/cross:arm-unknown-linux-gnueabihf \
|
os: ubuntu-latest
|
||||||
# arm-linux-gnueabihf-strip \
|
rust: stable
|
||||||
# /target/arm-unknown-linux-gnueabihf/debug/rg
|
target: armv7-unknown-linux-musleabihf
|
||||||
target: arm-unknown-linux-gnueabihf
|
- build: stable-arm-musleabi
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: armv7-unknown-linux-musleabi
|
||||||
|
- build: stable-powerpc64
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: powerpc64-unknown-linux-gnu
|
||||||
|
- build: stable-s390x
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: s390x-unknown-linux-gnu
|
||||||
- build: macos
|
- build: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
rust: nightly
|
rust: nightly
|
||||||
- build: win-msvc
|
- build: win-msvc
|
||||||
os: windows-2019
|
os: windows-2022
|
||||||
rust: nightly
|
rust: nightly
|
||||||
- build: win-gnu
|
- build: win-gnu
|
||||||
os: windows-2019
|
os: windows-2022
|
||||||
rust: nightly-x86_64-gnu
|
rust: nightly-x86_64-gnu
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install packages (Ubuntu)
|
- name: Install packages (Ubuntu)
|
||||||
if: matrix.os == 'ubuntu-18.04'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
ci/ubuntu-install-packages
|
ci/ubuntu-install-packages
|
||||||
|
|
||||||
- name: Install packages (macOS)
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
run: |
|
|
||||||
ci/macos-install-packages
|
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Use Cross
|
- name: Use Cross
|
||||||
if: matrix.target != ''
|
if: matrix.os == 'ubuntu-latest' && matrix.target != ''
|
||||||
run: |
|
run: |
|
||||||
cargo install cross
|
# In the past, new releases of 'cross' have broken CI. So for now, we
|
||||||
|
# pin it. We also use their pre-compiled binary releases because cross
|
||||||
|
# has over 100 dependencies and takes a bit to compile.
|
||||||
|
dir="$RUNNER_TEMP/cross-download"
|
||||||
|
mkdir "$dir"
|
||||||
|
echo "$dir" >> $GITHUB_PATH
|
||||||
|
cd "$dir"
|
||||||
|
curl -LO "https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
tar xf cross-x86_64-unknown-linux-musl.tar.gz
|
||||||
echo "CARGO=cross" >> $GITHUB_ENV
|
echo "CARGO=cross" >> $GITHUB_ENV
|
||||||
echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
|
echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
|
||||||
echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
|
echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
|
||||||
@ -116,6 +138,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "cargo command is: ${{ env.CARGO }}"
|
echo "cargo command is: ${{ env.CARGO }}"
|
||||||
echo "target flag is: ${{ env.TARGET_FLAGS }}"
|
echo "target flag is: ${{ env.TARGET_FLAGS }}"
|
||||||
|
echo "target dir is: ${{ env.TARGET_DIR }}"
|
||||||
|
|
||||||
- name: Build ripgrep and all crates
|
- name: Build ripgrep and all crates
|
||||||
run: ${{ env.CARGO }} build --verbose --workspace ${{ env.TARGET_FLAGS }}
|
run: ${{ env.CARGO }} build --verbose --workspace ${{ env.TARGET_FLAGS }}
|
||||||
@ -149,64 +172,60 @@ jobs:
|
|||||||
if: matrix.target != ''
|
if: matrix.target != ''
|
||||||
run: ${{ env.CARGO }} test --verbose --workspace ${{ env.TARGET_FLAGS }}
|
run: ${{ env.CARGO }} test --verbose --workspace ${{ env.TARGET_FLAGS }}
|
||||||
|
|
||||||
- name: Test for existence of build artifacts (Windows)
|
|
||||||
if: matrix.os == 'windows-2019'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
outdir="$(ci/cargo-out-dir "${{ env.TARGET_DIR }}")"
|
|
||||||
ls "$outdir/_rg.ps1" && file "$outdir/_rg.ps1"
|
|
||||||
|
|
||||||
- name: Test for existence of build artifacts (Unix)
|
|
||||||
if: matrix.os != 'windows-2019'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
outdir="$(ci/cargo-out-dir "${{ env.TARGET_DIR }}")"
|
|
||||||
# TODO: Check for the man page generation here. For whatever reason,
|
|
||||||
# it seems to be intermittently failing in CI. No idea why.
|
|
||||||
# for f in rg.bash rg.fish rg.1; do
|
|
||||||
for f in rg.bash rg.fish; do
|
|
||||||
# We could use file -E here, but it isn't supported on macOS.
|
|
||||||
ls "$outdir/$f" && file "$outdir/$f"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Test zsh shell completions (Unix, sans cross)
|
- name: Test zsh shell completions (Unix, sans cross)
|
||||||
# We could test this when using Cross, but we'd have to execute the
|
# We could test this when using Cross, but we'd have to execute the
|
||||||
# 'rg' binary (done in test-complete) with qemu, which is a pain and
|
# 'rg' binary (done in test-complete) with qemu, which is a pain and
|
||||||
# doesn't really gain us much. If shell completion works in one place,
|
# doesn't really gain us much. If shell completion works in one place,
|
||||||
# it probably works everywhere.
|
# it probably works everywhere.
|
||||||
if: matrix.target == '' && matrix.os != 'windows-2019'
|
if: matrix.target == '' && matrix.os != 'windows-2022'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ci/test-complete
|
run: ci/test-complete
|
||||||
|
|
||||||
rustfmt:
|
- name: Print hostname detected by grep-cli crate
|
||||||
name: rustfmt
|
shell: bash
|
||||||
runs-on: ubuntu-18.04
|
run: ${{ env.CARGO }} test --manifest-path crates/cli/Cargo.toml ${{ env.TARGET_FLAGS }} --lib print_hostname -- --nocapture
|
||||||
|
|
||||||
|
- name: Print available short flags
|
||||||
|
shell: bash
|
||||||
|
run: ${{ env.CARGO }} test --bin rg ${{ env.TARGET_FLAGS }} flags::defs::tests::available_shorts -- --nocapture
|
||||||
|
|
||||||
|
# Setup and compile on the wasm32-wasip1 target
|
||||||
|
wasm:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
- name: Add wasm32-wasip1 target
|
||||||
|
run: rustup target add wasm32-wasip1
|
||||||
|
- name: Basic build
|
||||||
|
run: cargo build --verbose
|
||||||
|
|
||||||
|
rustfmt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: |
|
run: cargo fmt --all --check
|
||||||
cargo fmt --all -- --check
|
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
name: Docs
|
runs-on: ubuntu-latest
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
- name: Check documentation
|
- name: Check documentation
|
||||||
env:
|
env:
|
||||||
RUSTDOCFLAGS: -D warnings
|
RUSTDOCFLAGS: -D warnings
|
||||||
|
371
.github/workflows/release.yml
vendored
371
.github/workflows/release.yml
vendored
@ -1,54 +1,43 @@
|
|||||||
# The way this works is the following:
|
|
||||||
#
|
|
||||||
# The create-release job runs purely to initialize the GitHub release itself
|
|
||||||
# and to output upload_url for the following job.
|
|
||||||
#
|
|
||||||
# The build-release job runs only once create-release is finished. It gets the
|
|
||||||
# release upload URL from create-release job outputs, then builds the release
|
|
||||||
# executables for each supported platform and attaches them as release assets
|
|
||||||
# to the previously created release.
|
|
||||||
#
|
|
||||||
# The key here is that we create the release only once.
|
|
||||||
#
|
|
||||||
# Reference:
|
|
||||||
# https://eugene-babichenko.github.io/blog/2020/05/09/github-actions-cross-platform-auto-releases/
|
|
||||||
|
|
||||||
name: release
|
name: release
|
||||||
|
|
||||||
|
# Only do the release on x.y.z tags.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
# Enable when testing release infrastructure on a branch.
|
|
||||||
# branches:
|
|
||||||
# - ag/work
|
|
||||||
tags:
|
tags:
|
||||||
- "[0-9]+.[0-9]+.[0-9]+"
|
- "[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
|
||||||
|
# We need this to be able to create releases.
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# The create-release job runs purely to initialize the GitHub release itself,
|
||||||
|
# and names the release after the `x.y.z` tag that was pushed. It's separate
|
||||||
|
# from building the release so that we only create the release once.
|
||||||
create-release:
|
create-release:
|
||||||
name: create-release
|
name: create-release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# env:
|
|
||||||
# Set to force version number, e.g., when no tag exists.
|
|
||||||
# RG_VERSION: TEST-0.0.0
|
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.release.outputs.upload_url }}
|
|
||||||
rg_version: ${{ env.RG_VERSION }}
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- name: Get the release version from the tag
|
- name: Get the release version from the tag
|
||||||
shell: bash
|
if: env.VERSION == ''
|
||||||
if: env.RG_VERSION == ''
|
run: echo "VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||||
|
- name: Show the version
|
||||||
run: |
|
run: |
|
||||||
# Apparently, this is the right way to get a tag name. Really?
|
echo "version is: $VERSION"
|
||||||
#
|
- name: Check that tag version and Cargo.toml version are the same
|
||||||
# See: https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
shell: bash
|
||||||
echo "RG_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
run: |
|
||||||
echo "version is: ${{ env.RG_VERSION }}"
|
if ! grep -q "version = \"$VERSION\"" Cargo.toml; then
|
||||||
|
echo "version does not match Cargo.toml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
- name: Create GitHub release
|
- name: Create GitHub release
|
||||||
id: release
|
|
||||||
uses: actions/create-release@v1
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
run: gh release create $VERSION --draft --verify-tag --title $VERSION
|
||||||
tag_name: ${{ env.RG_VERSION }}
|
outputs:
|
||||||
release_name: ${{ env.RG_VERSION }}
|
version: ${{ env.VERSION }}
|
||||||
|
|
||||||
build-release:
|
build-release:
|
||||||
name: build-release
|
name: build-release
|
||||||
@ -59,126 +48,324 @@ jobs:
|
|||||||
# systems.
|
# systems.
|
||||||
CARGO: cargo
|
CARGO: cargo
|
||||||
# When CARGO is set to CROSS, this is set to `--target matrix.target`.
|
# When CARGO is set to CROSS, this is set to `--target matrix.target`.
|
||||||
TARGET_FLAGS: ""
|
TARGET_FLAGS:
|
||||||
# When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
|
# When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
|
||||||
TARGET_DIR: ./target
|
TARGET_DIR: ./target
|
||||||
|
# Bump this as appropriate. We pin to a version to make sure CI
|
||||||
|
# continues to work as cross releases in the past have broken things
|
||||||
|
# in subtle ways.
|
||||||
|
CROSS_VERSION: v0.2.5
|
||||||
# Emit backtraces on panics.
|
# Emit backtraces on panics.
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
# Build static releases with PCRE2.
|
# Build static releases with PCRE2.
|
||||||
PCRE2_SYS_STATIC: 1
|
PCRE2_SYS_STATIC: 1
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
build: [linux, linux-arm, macos, win-msvc, win-gnu, win32-msvc]
|
|
||||||
include:
|
include:
|
||||||
- build: linux
|
- build: linux
|
||||||
os: ubuntu-18.04
|
os: ubuntu-latest
|
||||||
rust: nightly
|
rust: nightly
|
||||||
target: x86_64-unknown-linux-musl
|
target: x86_64-unknown-linux-musl
|
||||||
- build: linux-arm
|
strip: x86_64-linux-musl-strip
|
||||||
os: ubuntu-18.04
|
- build: stable-x86
|
||||||
rust: nightly
|
os: ubuntu-latest
|
||||||
target: arm-unknown-linux-gnueabihf
|
rust: stable
|
||||||
|
target: i686-unknown-linux-gnu
|
||||||
|
strip: x86_64-linux-gnu-strip
|
||||||
|
qemu: i386
|
||||||
|
- build: stable-aarch64
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
strip: aarch64-linux-gnu-strip
|
||||||
|
qemu: qemu-aarch64
|
||||||
|
- build: stable-arm-gnueabihf
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: armv7-unknown-linux-gnueabihf
|
||||||
|
strip: arm-linux-gnueabihf-strip
|
||||||
|
qemu: qemu-arm
|
||||||
|
- build: stable-arm-musleabihf
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: armv7-unknown-linux-musleabihf
|
||||||
|
strip: arm-linux-musleabihf-strip
|
||||||
|
qemu: qemu-arm
|
||||||
|
- build: stable-arm-musleabi
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: armv7-unknown-linux-musleabi
|
||||||
|
strip: arm-linux-musleabi-strip
|
||||||
|
qemu: qemu-arm
|
||||||
|
- build: stable-powerpc64
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: powerpc64-unknown-linux-gnu
|
||||||
|
strip: powerpc64-linux-gnu-strip
|
||||||
|
qemu: qemu-ppc64
|
||||||
|
- build: stable-s390x
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: s390x-unknown-linux-gnu
|
||||||
|
strip: s390x-linux-gnu-strip
|
||||||
|
qemu: qemu-s390x
|
||||||
- build: macos
|
- build: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
rust: nightly
|
rust: nightly
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
- build: win-msvc
|
- build: win-msvc
|
||||||
os: windows-2019
|
os: windows-latest
|
||||||
rust: nightly
|
rust: nightly
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
- build: win-gnu
|
- build: win-gnu
|
||||||
os: windows-2019
|
os: windows-latest
|
||||||
rust: nightly-x86_64-gnu
|
rust: nightly-x86_64-gnu
|
||||||
target: x86_64-pc-windows-gnu
|
target: x86_64-pc-windows-gnu
|
||||||
- build: win32-msvc
|
- build: win32-msvc
|
||||||
os: windows-2019
|
os: windows-latest
|
||||||
rust: nightly
|
rust: nightly
|
||||||
target: i686-pc-windows-msvc
|
target: i686-pc-windows-msvc
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Install packages (Ubuntu)
|
- name: Install packages (Ubuntu)
|
||||||
if: matrix.os == 'ubuntu-18.04'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ci/ubuntu-install-packages
|
ci/ubuntu-install-packages
|
||||||
|
|
||||||
- name: Install packages (macOS)
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
run: |
|
|
||||||
ci/macos-install-packages
|
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Use Cross
|
- name: Use Cross
|
||||||
|
if: matrix.os == 'ubuntu-latest' && matrix.target != ''
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cargo install cross
|
# In the past, new releases of 'cross' have broken CI. So for now, we
|
||||||
|
# pin it. We also use their pre-compiled binary releases because cross
|
||||||
|
# has over 100 dependencies and takes a bit to compile.
|
||||||
|
dir="$RUNNER_TEMP/cross-download"
|
||||||
|
mkdir "$dir"
|
||||||
|
echo "$dir" >> $GITHUB_PATH
|
||||||
|
cd "$dir"
|
||||||
|
curl -LO "https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
tar xf cross-x86_64-unknown-linux-musl.tar.gz
|
||||||
echo "CARGO=cross" >> $GITHUB_ENV
|
echo "CARGO=cross" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set target variables
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
|
echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
|
||||||
echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
|
echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Show command used for Cargo
|
- name: Show command used for Cargo
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "cargo command is: ${{ env.CARGO }}"
|
echo "cargo command is: ${{ env.CARGO }}"
|
||||||
echo "target flag is: ${{ env.TARGET_FLAGS }}"
|
echo "target flag is: ${{ env.TARGET_FLAGS }}"
|
||||||
echo "target dir is: ${{ env.TARGET_DIR }}"
|
echo "target dir is: ${{ env.TARGET_DIR }}"
|
||||||
|
|
||||||
- name: Build release binary
|
- name: Build release binary
|
||||||
run: ${{ env.CARGO }} build --verbose --release --features pcre2 ${{ env.TARGET_FLAGS }}
|
shell: bash
|
||||||
|
run: |
|
||||||
|
${{ env.CARGO }} build --verbose --release --features pcre2 ${{ env.TARGET_FLAGS }}
|
||||||
|
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||||
|
bin="target/${{ matrix.target }}/release/rg.exe"
|
||||||
|
else
|
||||||
|
bin="target/${{ matrix.target }}/release/rg"
|
||||||
|
fi
|
||||||
|
echo "BIN=$bin" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Strip release binary (linux and macos)
|
- name: Strip release binary (macos)
|
||||||
if: matrix.build == 'linux' || matrix.build == 'macos'
|
if: matrix.os == 'macos-latest'
|
||||||
run: strip "target/${{ matrix.target }}/release/rg"
|
shell: bash
|
||||||
|
run: strip "$BIN"
|
||||||
|
|
||||||
- name: Strip release binary (arm)
|
- name: Strip release binary (cross)
|
||||||
if: matrix.build == 'linux-arm'
|
if: env.CARGO == 'cross'
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
docker run --rm -v \
|
docker run --rm -v \
|
||||||
"$PWD/target:/target:Z" \
|
"$PWD/target:/target:Z" \
|
||||||
rustembedded/cross:arm-unknown-linux-gnueabihf \
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
arm-linux-gnueabihf-strip \
|
"${{ matrix.strip }}" \
|
||||||
/target/arm-unknown-linux-gnueabihf/release/rg
|
"/$BIN"
|
||||||
|
|
||||||
- name: Build archive
|
- name: Determine archive name
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
outdir="$(ci/cargo-out-dir "${{ env.TARGET_DIR }}")"
|
version="${{ needs.create-release.outputs.version }}"
|
||||||
staging="ripgrep-${{ needs.create-release.outputs.rg_version }}-${{ matrix.target }}"
|
echo "ARCHIVE=ripgrep-$version-${{ matrix.target }}" >> $GITHUB_ENV
|
||||||
mkdir -p "$staging"/{complete,doc}
|
|
||||||
|
|
||||||
cp {README.md,COPYING,UNLICENSE,LICENSE-MIT} "$staging/"
|
- name: Creating directory for archive
|
||||||
cp {CHANGELOG.md,FAQ.md,GUIDE.md} "$staging/doc/"
|
shell: bash
|
||||||
cp "$outdir"/{rg.bash,rg.fish,_rg.ps1} "$staging/complete/"
|
run: |
|
||||||
cp complete/_rg "$staging/complete/"
|
mkdir -p "$ARCHIVE"/{complete,doc}
|
||||||
|
cp "$BIN" "$ARCHIVE"/
|
||||||
|
cp {README.md,COPYING,UNLICENSE,LICENSE-MIT} "$ARCHIVE"/
|
||||||
|
cp {CHANGELOG.md,FAQ.md,GUIDE.md} "$ARCHIVE"/doc/
|
||||||
|
|
||||||
if [ "${{ matrix.os }}" = "windows-2019" ]; then
|
- name: Generate man page and completions (no emulation)
|
||||||
cp "target/${{ matrix.target }}/release/rg.exe" "$staging/"
|
if: matrix.qemu == ''
|
||||||
7z a "$staging.zip" "$staging"
|
shell: bash
|
||||||
echo "ASSET=$staging.zip" >> $GITHUB_ENV
|
run: |
|
||||||
else
|
"$BIN" --version
|
||||||
# The man page is only generated on Unix systems. ¯\_(ツ)_/¯
|
"$BIN" --generate complete-bash > "$ARCHIVE/complete/rg.bash"
|
||||||
cp "$outdir"/rg.1 "$staging/doc/"
|
"$BIN" --generate complete-fish > "$ARCHIVE/complete/rg.fish"
|
||||||
cp "target/${{ matrix.target }}/release/rg" "$staging/"
|
"$BIN" --generate complete-powershell > "$ARCHIVE/complete/_rg.ps1"
|
||||||
tar czf "$staging.tar.gz" "$staging"
|
"$BIN" --generate complete-zsh > "$ARCHIVE/complete/_rg"
|
||||||
echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
|
"$BIN" --generate man > "$ARCHIVE/doc/rg.1"
|
||||||
fi
|
|
||||||
|
- name: Generate man page and completions (emulation)
|
||||||
|
if: matrix.qemu != ''
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
docker run --rm -v \
|
||||||
|
"$PWD/target:/target:Z" \
|
||||||
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
|
"${{ matrix.qemu }}" "/$BIN" --version
|
||||||
|
docker run --rm -v \
|
||||||
|
"$PWD/target:/target:Z" \
|
||||||
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
|
--generate complete-bash > "$ARCHIVE/complete/rg.bash"
|
||||||
|
docker run --rm -v \
|
||||||
|
"$PWD/target:/target:Z" \
|
||||||
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
|
--generate complete-fish > "$ARCHIVE/complete/rg.fish"
|
||||||
|
docker run --rm -v \
|
||||||
|
"$PWD/target:/target:Z" \
|
||||||
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
|
--generate complete-powershell > "$ARCHIVE/complete/_rg.ps1"
|
||||||
|
docker run --rm -v \
|
||||||
|
"$PWD/target:/target:Z" \
|
||||||
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
|
--generate complete-zsh > "$ARCHIVE/complete/_rg"
|
||||||
|
docker run --rm -v \
|
||||||
|
"$PWD/target:/target:Z" \
|
||||||
|
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||||
|
"${{ matrix.qemu }}" "/$BIN" \
|
||||||
|
--generate man > "$ARCHIVE/doc/rg.1"
|
||||||
|
|
||||||
|
- name: Build archive (Windows)
|
||||||
|
shell: bash
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: |
|
||||||
|
7z a "$ARCHIVE.zip" "$ARCHIVE"
|
||||||
|
certutil -hashfile "$ARCHIVE.zip" SHA256 > "$ARCHIVE.zip.sha256"
|
||||||
|
echo "ASSET=$ARCHIVE.zip" >> $GITHUB_ENV
|
||||||
|
echo "ASSET_SUM=$ARCHIVE.zip.sha256" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build archive (Unix)
|
||||||
|
shell: bash
|
||||||
|
if: matrix.os != 'windows-latest'
|
||||||
|
run: |
|
||||||
|
tar czf "$ARCHIVE.tar.gz" "$ARCHIVE"
|
||||||
|
shasum -a 256 "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256"
|
||||||
|
echo "ASSET=$ARCHIVE.tar.gz" >> $GITHUB_ENV
|
||||||
|
echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload release archive
|
- name: Upload release archive
|
||||||
uses: actions/upload-release-asset@v1.0.1
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
version="${{ needs.create-release.outputs.version }}"
|
||||||
|
gh release upload "$version" ${{ env.ASSET }} ${{ env.ASSET_SUM }}
|
||||||
|
|
||||||
|
build-release-deb:
|
||||||
|
name: build-release-deb
|
||||||
|
needs: ['create-release']
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
TARGET: x86_64-unknown-linux-musl
|
||||||
|
# Emit backtraces on panics.
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
# Since we're distributing the dpkg, we don't know whether the user will
|
||||||
|
# have PCRE2 installed, so just do a static build.
|
||||||
|
PCRE2_SYS_STATIC: 1
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install packages (Ubuntu)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
ci/ubuntu-install-packages
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
toolchain: nightly
|
||||||
asset_path: ${{ env.ASSET }}
|
target: ${{ env.TARGET }}
|
||||||
asset_name: ${{ env.ASSET }}
|
|
||||||
asset_content_type: application/octet-stream
|
- name: Install cargo-deb
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cargo install cargo-deb
|
||||||
|
|
||||||
|
# 'cargo deb' does not seem to provide a way to specify an asset that is
|
||||||
|
# created at build time, such as ripgrep's man page. To work around this,
|
||||||
|
# we force a debug build, copy out the man page (and shell completions)
|
||||||
|
# produced from that build, put it into a predictable location and then
|
||||||
|
# build the deb, which knows where to look.
|
||||||
|
- name: Build debug binary to create release assets
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cargo build --target ${{ env.TARGET }}
|
||||||
|
bin="target/${{ env.TARGET }}/debug/rg"
|
||||||
|
echo "BIN=$bin" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create deployment directory
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
dir=deployment/deb
|
||||||
|
mkdir -p "$dir"
|
||||||
|
echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Generate man page
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
"$BIN" --generate man > "$DEPLOY_DIR/rg.1"
|
||||||
|
|
||||||
|
- name: Generate shell completions
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
"$BIN" --generate complete-bash > "$DEPLOY_DIR/rg.bash"
|
||||||
|
"$BIN" --generate complete-fish > "$DEPLOY_DIR/rg.fish"
|
||||||
|
"$BIN" --generate complete-zsh > "$DEPLOY_DIR/_rg"
|
||||||
|
|
||||||
|
- name: Build release binary
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cargo deb --profile deb --target ${{ env.TARGET }}
|
||||||
|
version="${{ needs.create-release.outputs.version }}"
|
||||||
|
echo "DEB_DIR=target/${{ env.TARGET }}/debian" >> $GITHUB_ENV
|
||||||
|
echo "DEB_NAME=ripgrep_$version-1_amd64.deb" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create sha256 sum of deb file
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd "$DEB_DIR"
|
||||||
|
sum="$DEB_NAME.sha256"
|
||||||
|
shasum -a 256 "$DEB_NAME" > "$sum"
|
||||||
|
echo "SUM=$sum" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Upload release archive
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd "$DEB_DIR"
|
||||||
|
version="${{ needs.create-release.outputs.version }}"
|
||||||
|
gh release upload "$version" "$DEB_NAME" "$SUM"
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@ target
|
|||||||
/termcolor/Cargo.lock
|
/termcolor/Cargo.lock
|
||||||
/wincolor/Cargo.lock
|
/wincolor/Cargo.lock
|
||||||
/deployment
|
/deployment
|
||||||
|
/.idea
|
||||||
|
|
||||||
# Snapcraft files
|
# Snapcraft files
|
||||||
stage
|
stage
|
||||||
|
239
CHANGELOG.md
239
CHANGELOG.md
@ -1,3 +1,242 @@
|
|||||||
|
TBD
|
||||||
|
===
|
||||||
|
Unreleased changes. Release notes have not yet been written.
|
||||||
|
|
||||||
|
|
||||||
|
14.1.1 (2024-09-08)
|
||||||
|
===================
|
||||||
|
This is a minor release with a bug fix for a matching bug. In particular, a bug
|
||||||
|
was found that could cause ripgrep to ignore lines that should match. That is,
|
||||||
|
false negatives. It is difficult to characterize the specific set of regexes
|
||||||
|
in which this occurs as it requires multiple different optimization strategies
|
||||||
|
to collide and produce an incorrect result. But as one reported example, in
|
||||||
|
ripgrep, the regex `(?i:e.x|ex)` does not match `e-x` when it should. (This
|
||||||
|
bug is a result of an inner literal optimization performed in the `grep-regex`
|
||||||
|
crate and not in the `regex` crate.)
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #2884](https://github.com/BurntSushi/ripgrep/issues/2884):
|
||||||
|
Fix bug where ripgrep could miss some matches that it should report.
|
||||||
|
|
||||||
|
Miscellaneous:
|
||||||
|
|
||||||
|
* [MISC #2748](https://github.com/BurntSushi/ripgrep/issues/2748):
|
||||||
|
Remove ripgrep's `simd-accel` feature because it was frequently broken.
|
||||||
|
|
||||||
|
|
||||||
|
14.1.0 (2024-01-06)
|
||||||
|
===================
|
||||||
|
This is a minor release with a few small new features and bug fixes. This
|
||||||
|
release contains a bug fix for unbounded memory growth while walking a
|
||||||
|
directory tree. This release also includes improvements to the completions for
|
||||||
|
the `fish` shell, and release binaries for several additional ARM targets.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #2664](https://github.com/BurntSushi/ripgrep/issues/2690):
|
||||||
|
Fix unbounded memory growth in the `ignore` crate.
|
||||||
|
|
||||||
|
Feature enhancements:
|
||||||
|
|
||||||
|
* Added or improved file type filtering for Lean and Meson.
|
||||||
|
* [FEATURE #2684](https://github.com/BurntSushi/ripgrep/issues/2684):
|
||||||
|
Improve completions for the `fish` shell.
|
||||||
|
* [FEATURE #2702](https://github.com/BurntSushi/ripgrep/pull/2702):
|
||||||
|
Add release binaries for `armv7-unknown-linux-gnueabihf`,
|
||||||
|
`armv7-unknown-linux-musleabihf` and `armv7-unknown-linux-musleabi`.
|
||||||
|
|
||||||
|
|
||||||
|
14.0.3 (2023-11-28)
|
||||||
|
===================
|
||||||
|
This is a patch release with a bug fix for the `--sortr` flag.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #2664](https://github.com/BurntSushi/ripgrep/issues/2664):
|
||||||
|
Fix `--sortr=path`. I left a `todo!()` in the source. Oof.
|
||||||
|
|
||||||
|
|
||||||
|
14.0.2 (2023-11-27)
|
||||||
|
===================
|
||||||
|
This is a patch release with a few small bug fixes.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #2654](https://github.com/BurntSushi/ripgrep/issues/2654):
|
||||||
|
Fix `deb` release sha256 sum file.
|
||||||
|
* [BUG #2658](https://github.com/BurntSushi/ripgrep/issues/2658):
|
||||||
|
Fix partial regression in the behavior of `--null-data --line-regexp`.
|
||||||
|
* [BUG #2659](https://github.com/BurntSushi/ripgrep/issues/2659):
|
||||||
|
Fix Fish shell completions.
|
||||||
|
* [BUG #2662](https://github.com/BurntSushi/ripgrep/issues/2662):
|
||||||
|
Fix typo in documentation for `-i/--ignore-case`.
|
||||||
|
|
||||||
|
|
||||||
|
14.0.1 (2023-11-26)
|
||||||
|
===================
|
||||||
|
This a patch release meant to fix `cargo install ripgrep` on Windows.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #2653](https://github.com/BurntSushi/ripgrep/issues/2653):
|
||||||
|
Include `pkg/windows/Manifest.xml` in crate package.
|
||||||
|
|
||||||
|
|
||||||
|
14.0.0 (2023-11-26)
|
||||||
|
===================
|
||||||
|
ripgrep 14 is a new major version release of ripgrep that has some new
|
||||||
|
features, performance improvements and a lot of bug fixes.
|
||||||
|
|
||||||
|
The headlining feature in this release is hyperlink support. In this release,
|
||||||
|
they are an opt-in feature but may change to an opt-out feature in the future.
|
||||||
|
To enable them, try passing `--hyperlink-format default`. If you use [VS Code],
|
||||||
|
then try passing `--hyperlink-format vscode`. Please [report your experience
|
||||||
|
with hyperlinks][report-hyperlinks], positive or negative.
|
||||||
|
|
||||||
|
[VS Code]: https://code.visualstudio.com/
|
||||||
|
[report-hyperlinks]: https://github.com/BurntSushi/ripgrep/discussions/2611
|
||||||
|
|
||||||
|
Another headlining development in this release is that it contains a rewrite
|
||||||
|
of its regex engine. You generally shouldn't notice any changes, except for
|
||||||
|
some searches may get faster. You can read more about the [regex engine rewrite
|
||||||
|
on my blog][regex-internals]. Please [report your performance improvements or
|
||||||
|
regressions that you notice][report-perf].
|
||||||
|
|
||||||
|
[report-perf]: https://github.com/BurntSushi/ripgrep/discussions/2652
|
||||||
|
|
||||||
|
Finally, ripgrep switched the library it uses for argument parsing. Users
|
||||||
|
should not notice a difference in most cases (error messages have changed
|
||||||
|
somewhat), but flag overrides should generally be more consistent. For example,
|
||||||
|
things like `--no-ignore --ignore-vcs` work as one would expect (disables all
|
||||||
|
filtering related to ignore rules except for rules found in version control
|
||||||
|
systems such as `git`).
|
||||||
|
|
||||||
|
[regex-internals]: https://blog.burntsushi.net/regex-internals/
|
||||||
|
|
||||||
|
**BREAKING CHANGES**:
|
||||||
|
|
||||||
|
* `rg -C1 -A2` used to be equivalent to `rg -A2`, but now it is equivalent to
|
||||||
|
`rg -B1 -A2`. That is, `-A` and `-B` no longer completely override `-C`.
|
||||||
|
Instead, they only partially override `-C`.
|
||||||
|
|
||||||
|
Build process changes:
|
||||||
|
|
||||||
|
* ripgrep's shell completions and man page are now created by running ripgrep
|
||||||
|
with a new `--generate` flag. For example, `rg --generate man` will write a
|
||||||
|
man page in `roff` format on stdout. The release archives have not changed.
|
||||||
|
* The optional build dependency on `asciidoc` or `asciidoctor` has been
|
||||||
|
dropped. Previously, it was used to produce ripgrep's man page. ripgrep now
|
||||||
|
owns this process itself by writing `roff` directly.
|
||||||
|
|
||||||
|
Performance improvements:
|
||||||
|
|
||||||
|
* [PERF #1746](https://github.com/BurntSushi/ripgrep/issues/1746):
|
||||||
|
Make some cases with inner literals faster.
|
||||||
|
* [PERF #1760](https://github.com/BurntSushi/ripgrep/issues/1760):
|
||||||
|
Make most searches with `\b` look-arounds (among others) much faster.
|
||||||
|
* [PERF #2591](https://github.com/BurntSushi/ripgrep/pull/2591):
|
||||||
|
Parallel directory traversal now uses work stealing for faster searches.
|
||||||
|
* [PERF #2642](https://github.com/BurntSushi/ripgrep/pull/2642):
|
||||||
|
Parallel directory traversal has some contention reduced.
|
||||||
|
|
||||||
|
Feature enhancements:
|
||||||
|
|
||||||
|
* Added or improved file type filtering for Ada, DITA, Elixir, Fuchsia, Gentoo,
|
||||||
|
Gradle, GraphQL, Markdown, Prolog, Raku, TypeScript, USD, V
|
||||||
|
* [FEATURE #665](https://github.com/BurntSushi/ripgrep/issues/665):
|
||||||
|
Add a new `--hyperlink-format` flag that turns file paths into hyperlinks.
|
||||||
|
* [FEATURE #1709](https://github.com/BurntSushi/ripgrep/issues/1709):
|
||||||
|
Improve documentation of ripgrep's behavior when stdout is a tty.
|
||||||
|
* [FEATURE #1737](https://github.com/BurntSushi/ripgrep/issues/1737):
|
||||||
|
Provide binaries for Apple silicon.
|
||||||
|
* [FEATURE #1790](https://github.com/BurntSushi/ripgrep/issues/1790):
|
||||||
|
Add new `--stop-on-nonmatch` flag.
|
||||||
|
* [FEATURE #1814](https://github.com/BurntSushi/ripgrep/issues/1814):
|
||||||
|
Flags are now categorized in `-h/--help` output and ripgrep's man page.
|
||||||
|
* [FEATURE #1838](https://github.com/BurntSushi/ripgrep/issues/1838):
|
||||||
|
An error is shown when searching for NUL bytes with binary detection enabled.
|
||||||
|
* [FEATURE #2195](https://github.com/BurntSushi/ripgrep/issues/2195):
|
||||||
|
When `extra-verbose` mode is enabled in zsh, show extra file type info.
|
||||||
|
* [FEATURE #2298](https://github.com/BurntSushi/ripgrep/issues/2298):
|
||||||
|
Add instructions for installing ripgrep using `cargo binstall`.
|
||||||
|
* [FEATURE #2409](https://github.com/BurntSushi/ripgrep/pull/2409):
|
||||||
|
Added installation instructions for `winget`.
|
||||||
|
* [FEATURE #2425](https://github.com/BurntSushi/ripgrep/pull/2425):
|
||||||
|
Shell completions (and man page) can be created via `rg --generate`.
|
||||||
|
* [FEATURE #2524](https://github.com/BurntSushi/ripgrep/issues/2524):
|
||||||
|
The `--debug` flag now indicates whether stdin or `./` is being searched.
|
||||||
|
* [FEATURE #2643](https://github.com/BurntSushi/ripgrep/issues/2643):
|
||||||
|
Make `-d` a short flag for `--max-depth`.
|
||||||
|
* [FEATURE #2645](https://github.com/BurntSushi/ripgrep/issues/2645):
|
||||||
|
The `--version` output will now also contain PCRE2 availability information.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #884](https://github.com/BurntSushi/ripgrep/issues/884):
|
||||||
|
Don't error when `-v/--invert-match` is used multiple times.
|
||||||
|
* [BUG #1275](https://github.com/BurntSushi/ripgrep/issues/1275):
|
||||||
|
Fix bug with `\b` assertion in the regex engine.
|
||||||
|
* [BUG #1376](https://github.com/BurntSushi/ripgrep/issues/1376):
|
||||||
|
Using `--no-ignore --ignore-vcs` now works as one would expect.
|
||||||
|
* [BUG #1622](https://github.com/BurntSushi/ripgrep/issues/1622):
|
||||||
|
Add note about error messages to `-z/--search-zip` documentation.
|
||||||
|
* [BUG #1648](https://github.com/BurntSushi/ripgrep/issues/1648):
|
||||||
|
Fix bug where sometimes short flags with values, e.g., `-M 900`, would fail.
|
||||||
|
* [BUG #1701](https://github.com/BurntSushi/ripgrep/issues/1701):
|
||||||
|
Fix bug where some flags could not be repeated.
|
||||||
|
* [BUG #1757](https://github.com/BurntSushi/ripgrep/issues/1757):
|
||||||
|
Fix bug when searching a sub-directory didn't have ignores applied correctly.
|
||||||
|
* [BUG #1891](https://github.com/BurntSushi/ripgrep/issues/1891):
|
||||||
|
Fix bug when using `-w` with a regex that can match the empty string.
|
||||||
|
* [BUG #1911](https://github.com/BurntSushi/ripgrep/issues/1911):
|
||||||
|
Disable mmap searching in all non-64-bit environments.
|
||||||
|
* [BUG #1966](https://github.com/BurntSushi/ripgrep/issues/1966):
|
||||||
|
Fix bug where ripgrep can panic when printing to stderr.
|
||||||
|
* [BUG #2046](https://github.com/BurntSushi/ripgrep/issues/2046):
|
||||||
|
Clarify that `--pre` can accept any kind of path in the documentation.
|
||||||
|
* [BUG #2108](https://github.com/BurntSushi/ripgrep/issues/2108):
|
||||||
|
Improve docs for `-r/--replace` syntax.
|
||||||
|
* [BUG #2198](https://github.com/BurntSushi/ripgrep/issues/2198):
|
||||||
|
Fix bug where `--no-ignore-dot` would not ignore `.rgignore`.
|
||||||
|
* [BUG #2201](https://github.com/BurntSushi/ripgrep/issues/2201):
|
||||||
|
Improve docs for `-r/--replace` flag.
|
||||||
|
* [BUG #2288](https://github.com/BurntSushi/ripgrep/issues/2288):
|
||||||
|
`-A` and `-B` now only each partially override `-C`.
|
||||||
|
* [BUG #2236](https://github.com/BurntSushi/ripgrep/issues/2236):
|
||||||
|
Fix gitignore parsing bug where a trailing `\/` resulted in an error.
|
||||||
|
* [BUG #2243](https://github.com/BurntSushi/ripgrep/issues/2243):
|
||||||
|
Fix `--sort` flag for values other than `path`.
|
||||||
|
* [BUG #2246](https://github.com/BurntSushi/ripgrep/issues/2246):
|
||||||
|
Add note in `--debug` logs when binary files are ignored.
|
||||||
|
* [BUG #2337](https://github.com/BurntSushi/ripgrep/issues/2337):
|
||||||
|
Improve docs to mention that `--stats` is always implied by `--json`.
|
||||||
|
* [BUG #2381](https://github.com/BurntSushi/ripgrep/issues/2381):
|
||||||
|
Make `-p/--pretty` override flags like `--no-line-number`.
|
||||||
|
* [BUG #2392](https://github.com/BurntSushi/ripgrep/issues/2392):
|
||||||
|
Improve global git config parsing of the `excludesFile` field.
|
||||||
|
* [BUG #2418](https://github.com/BurntSushi/ripgrep/pull/2418):
|
||||||
|
Clarify sorting semantics of `--sort=path`.
|
||||||
|
* [BUG #2458](https://github.com/BurntSushi/ripgrep/pull/2458):
|
||||||
|
Make `--trim` run before `-M/--max-columns` takes effect.
|
||||||
|
* [BUG #2479](https://github.com/BurntSushi/ripgrep/issues/2479):
|
||||||
|
Add documentation about `.ignore`/`.rgignore` files in parent directories.
|
||||||
|
* [BUG #2480](https://github.com/BurntSushi/ripgrep/issues/2480):
|
||||||
|
Fix bug when using inline regex flags with `-e/--regexp`.
|
||||||
|
* [BUG #2505](https://github.com/BurntSushi/ripgrep/issues/2505):
|
||||||
|
Improve docs for `--vimgrep` by mentioning footguns and some work-arounds.
|
||||||
|
* [BUG #2519](https://github.com/BurntSushi/ripgrep/issues/2519):
|
||||||
|
Fix incorrect default value in documentation for `--field-match-separator`.
|
||||||
|
* [BUG #2523](https://github.com/BurntSushi/ripgrep/issues/2523):
|
||||||
|
Make executable searching take `.com` into account on Windows.
|
||||||
|
* [BUG #2574](https://github.com/BurntSushi/ripgrep/issues/2574):
|
||||||
|
Fix bug in `-w/--word-regexp` that would result in incorrect match offsets.
|
||||||
|
* [BUG #2623](https://github.com/BurntSushi/ripgrep/issues/2623):
|
||||||
|
Fix a number of bugs with the `-w/--word-regexp` flag.
|
||||||
|
* [BUG #2636](https://github.com/BurntSushi/ripgrep/pull/2636):
|
||||||
|
Strip release binaries for macOS.
|
||||||
|
|
||||||
|
|
||||||
13.0.0 (2021-06-12)
|
13.0.0 (2021-06-12)
|
||||||
===================
|
===================
|
||||||
ripgrep 13 is a new major version release of ripgrep that primarily contains
|
ripgrep 13 is a new major version release of ripgrep that primarily contains
|
||||||
|
456
Cargo.lock
generated
456
Cargo.lock
generated
@ -4,68 +4,41 @@ version = 3
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "1.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "anyhow"
|
||||||
version = "0.2.14"
|
version = "1.0.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
|
||||||
dependencies = [
|
|
||||||
"hermit-abi",
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "0.2.16"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytecount"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.68"
|
version = "1.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
|
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
|
"libc",
|
||||||
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -73,45 +46,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "crossbeam-channel"
|
||||||
version = "2.33.3"
|
version = "0.5.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"crossbeam-utils",
|
||||||
"strsim",
|
|
||||||
"textwrap",
|
|
||||||
"unicode-width",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-deque"
|
||||||
version = "0.5.1"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.5"
|
version = "0.8.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
dependencies = [
|
|
||||||
"cfg-if 1.0.0",
|
|
||||||
"lazy_static",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.28"
|
version = "0.8.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
|
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if",
|
||||||
"packed_simd_2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -123,42 +97,29 @@ dependencies = [
|
|||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fnv"
|
|
||||||
version = "1.0.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fs_extra"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.7"
|
version = "0.4.16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"bstr",
|
"bstr",
|
||||||
"fnv",
|
|
||||||
"glob",
|
"glob",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep"
|
name = "grep"
|
||||||
version = "0.2.8"
|
version = "0.3.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grep-cli",
|
"grep-cli",
|
||||||
"grep-matcher",
|
"grep-matcher",
|
||||||
@ -172,22 +133,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-cli"
|
name = "grep-cli"
|
||||||
version = "0.1.6"
|
version = "0.1.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"bstr",
|
"bstr",
|
||||||
"globset",
|
"globset",
|
||||||
"lazy_static",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
|
||||||
"same-file",
|
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-matcher"
|
name = "grep-matcher"
|
||||||
version = "0.1.5"
|
version = "0.1.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex",
|
"regex",
|
||||||
@ -195,21 +153,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-pcre2"
|
name = "grep-pcre2"
|
||||||
version = "0.1.5"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grep-matcher",
|
"grep-matcher",
|
||||||
|
"log",
|
||||||
"pcre2",
|
"pcre2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-printer"
|
name = "grep-printer"
|
||||||
version = "0.1.6"
|
version = "0.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
|
||||||
"bstr",
|
"bstr",
|
||||||
"grep-matcher",
|
"grep-matcher",
|
||||||
"grep-regex",
|
"grep-regex",
|
||||||
"grep-searcher",
|
"grep-searcher",
|
||||||
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
@ -217,80 +176,67 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-regex"
|
name = "grep-regex"
|
||||||
version = "0.1.9"
|
version = "0.1.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
|
||||||
"bstr",
|
"bstr",
|
||||||
"grep-matcher",
|
"grep-matcher",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex-automata",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"thread_local",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-searcher"
|
name = "grep-searcher"
|
||||||
version = "0.1.8"
|
version = "0.1.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bstr",
|
"bstr",
|
||||||
"bytecount",
|
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"encoding_rs_io",
|
"encoding_rs_io",
|
||||||
"grep-matcher",
|
"grep-matcher",
|
||||||
"grep-regex",
|
"grep-regex",
|
||||||
"log",
|
"log",
|
||||||
|
"memchr",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hermit-abi"
|
|
||||||
version = "0.1.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.4.18"
|
version = "0.4.23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"crossbeam-utils",
|
"crossbeam-deque",
|
||||||
"globset",
|
"globset",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex",
|
"regex-automata",
|
||||||
"same-file",
|
"same-file",
|
||||||
"thread_local",
|
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.7"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jemalloc-sys"
|
name = "jemalloc-sys"
|
||||||
version = "0.3.2"
|
version = "0.5.4+5.3.0-patched"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
|
checksum = "ac6c1946e1cea1788cbfde01c993b52a10e2da07f4bac608228d1bed20bfebf2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"fs_extra",
|
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jemallocator"
|
name = "jemallocator"
|
||||||
version = "0.3.2"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69"
|
checksum = "a0de374a9f8e63150e6f5e8a60cc14c668226d7a347d8aee1a45766e3c4dd3bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jemalloc-sys",
|
"jemalloc-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@ -298,98 +244,62 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.22"
|
version = "0.1.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd"
|
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lexopt"
|
||||||
version = "1.4.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.97"
|
version = "0.2.158"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libm"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
dependencies = [
|
|
||||||
"cfg-if 1.0.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.4.0"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
version = "0.3.0"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20ff203f7bdc401350b1dbaa0355135777d25f41c0bbc601851bbd6cf61e8ff5"
|
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_cpus"
|
|
||||||
version = "1.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "packed_simd_2"
|
|
||||||
version = "0.3.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0e64858a2d3733fdd61adfdd6da89aa202f7ff0e741d2fc7ed1e452ba9dc99d7"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.10",
|
|
||||||
"libm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pcre2"
|
name = "pcre2"
|
||||||
version = "0.2.3"
|
version = "0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85b30f2f69903b439dd9dc9e824119b82a55bf113b29af8d70948a03c1b11ab1"
|
checksum = "3be55c43ac18044541d58d897e8f4c55157218428953ebd39d86df3ba0286b2b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"pcre2-sys",
|
"pcre2-sys",
|
||||||
"thread_local",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pcre2-sys"
|
name = "pcre2-sys"
|
||||||
version = "0.2.5"
|
version = "0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dec30e5e9ec37eb8fbf1dea5989bc957fd3df56fbee5061aa7b7a99dbb37b722"
|
checksum = "550f5d18fb1b90c20b87e161852c10cde77858c3900c5059b5ad2a1449f11d8a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@ -398,76 +308,81 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.19"
|
version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.27"
|
version = "1.0.86"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
|
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.9"
|
version = "1.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.10.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ripgrep"
|
name = "ripgrep"
|
||||||
version = "13.0.0"
|
version = "14.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"bstr",
|
"bstr",
|
||||||
"clap",
|
|
||||||
"grep",
|
"grep",
|
||||||
"ignore",
|
"ignore",
|
||||||
"jemallocator",
|
"jemallocator",
|
||||||
"lazy_static",
|
"lexopt",
|
||||||
"log",
|
"log",
|
||||||
"num_cpus",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@ -480,18 +395,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.126"
|
version = "1.0.210"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.126"
|
version = "1.0.210"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -500,109 +415,142 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.64"
|
version = "1.0.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "shlex"
|
||||||
version = "0.8.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.73"
|
version = "2.0.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
|
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-xid",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.1.2"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
|
||||||
dependencies = [
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "unicode-ident"
|
||||||
version = "1.1.3"
|
version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-width"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.3.2"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"same-file",
|
"same-file",
|
||||||
"winapi",
|
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.5"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "windows-sys"
|
||||||
version = "0.4.0"
|
version = "0.59.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
61
Cargo.toml
61
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ripgrep"
|
name = "ripgrep"
|
||||||
version = "13.0.0" #:version
|
version = "14.1.1" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
ripgrep is a line-oriented search tool that recursively searches the current
|
ripgrep is a line-oriented search tool that recursively searches the current
|
||||||
@ -13,10 +13,18 @@ repository = "https://github.com/BurntSushi/ripgrep"
|
|||||||
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
||||||
categories = ["command-line-utilities", "text-processing"]
|
categories = ["command-line-utilities", "text-processing"]
|
||||||
license = "Unlicense OR MIT"
|
license = "Unlicense OR MIT"
|
||||||
exclude = ["HomebrewFormula"]
|
exclude = [
|
||||||
|
"HomebrewFormula",
|
||||||
|
"/.github/",
|
||||||
|
"/ci/",
|
||||||
|
"/pkg/brew",
|
||||||
|
"/benchsuite/",
|
||||||
|
"/scripts/",
|
||||||
|
]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
autotests = false
|
autotests = false
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
rust-version = "1.72"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
bench = false
|
bench = false
|
||||||
@ -41,31 +49,18 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bstr = "0.2.12"
|
anyhow = "1.0.75"
|
||||||
grep = { version = "0.2.8", path = "crates/grep" }
|
bstr = "1.7.0"
|
||||||
ignore = { version = "0.4.18", path = "crates/ignore" }
|
grep = { version = "0.3.2", path = "crates/grep" }
|
||||||
lazy_static = "1.1.0"
|
ignore = { version = "0.4.23", path = "crates/ignore" }
|
||||||
|
lexopt = "0.3.0"
|
||||||
log = "0.4.5"
|
log = "0.4.5"
|
||||||
num_cpus = "1.8.0"
|
|
||||||
regex = "1.3.5"
|
|
||||||
serde_json = "1.0.23"
|
serde_json = "1.0.23"
|
||||||
termcolor = "1.1.0"
|
termcolor = "1.1.0"
|
||||||
|
textwrap = { version = "0.16.0", default-features = false }
|
||||||
[dependencies.clap]
|
|
||||||
version = "2.33.0"
|
|
||||||
default-features = false
|
|
||||||
features = ["suggestions"]
|
|
||||||
|
|
||||||
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
|
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
|
||||||
version = "0.3.0"
|
version = "0.5.0"
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
lazy_static = "1.1.0"
|
|
||||||
|
|
||||||
[build-dependencies.clap]
|
|
||||||
version = "2.33.0"
|
|
||||||
default-features = false
|
|
||||||
features = ["suggestions"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = "1.0.77"
|
serde = "1.0.77"
|
||||||
@ -73,12 +68,30 @@ serde_derive = "1.0.77"
|
|||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd-accel = ["grep/simd-accel"]
|
|
||||||
pcre2 = ["grep/pcre2"]
|
pcre2 = ["grep/pcre2"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 1
|
debug = 1
|
||||||
|
|
||||||
|
[profile.release-lto]
|
||||||
|
inherits = "release"
|
||||||
|
opt-level = 3
|
||||||
|
debug = "none"
|
||||||
|
strip = "symbols"
|
||||||
|
debug-assertions = false
|
||||||
|
overflow-checks = false
|
||||||
|
lto = "fat"
|
||||||
|
panic = "abort"
|
||||||
|
incremental = false
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
|
# This is the main way to strip binaries in the deb package created by
|
||||||
|
# 'cargo deb'. For other release binaries, we (currently) call 'strip'
|
||||||
|
# explicitly in the release process.
|
||||||
|
[profile.deb]
|
||||||
|
inherits = "release"
|
||||||
|
debug = false
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
features = ["pcre2"]
|
features = ["pcre2"]
|
||||||
section = "utils"
|
section = "utils"
|
||||||
|
11
Cross.toml
11
Cross.toml
@ -1,11 +0,0 @@
|
|||||||
[target.x86_64-unknown-linux-musl]
|
|
||||||
image = "burntsushi/cross:x86_64-unknown-linux-musl"
|
|
||||||
|
|
||||||
[target.i686-unknown-linux-gnu]
|
|
||||||
image = "burntsushi/cross:i686-unknown-linux-gnu"
|
|
||||||
|
|
||||||
[target.mips64-unknown-linux-gnuabi64]
|
|
||||||
image = "burntsushi/cross:mips64-unknown-linux-gnuabi64"
|
|
||||||
|
|
||||||
[target.arm-unknown-linux-gnueabihf]
|
|
||||||
image = "burntsushi/cross:arm-unknown-linux-gnueabihf"
|
|
93
FAQ.md
93
FAQ.md
@ -61,18 +61,24 @@ patch release out with a fix. However, no promises are made.
|
|||||||
Does ripgrep have a man page?
|
Does ripgrep have a man page?
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
Yes! Whenever ripgrep is compiled on a system with `asciidoctor` or `asciidoc`
|
Yes. If you installed ripgrep through a package manager on a Unix system, then
|
||||||
present, then a man page is generated from ripgrep's argv parser. After
|
it would have ideally been installed for you in the proper location. In which
|
||||||
compiling ripgrep, you can find the man page like so from the root of the
|
case, `man rg` should just work.
|
||||||
repository:
|
|
||||||
|
Otherwise, you can ask ripgrep to generate the man page:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ find ./target -name rg.1 -print0 | xargs -0 ls -t | head -n1
|
$ mkdir -p man/man1
|
||||||
./target/debug/build/ripgrep-79899d0edd4129ca/out/rg.1
|
$ rg --generate man > man/man1/rg.1
|
||||||
|
$ MANPATH="$PWD/man" man rg
|
||||||
```
|
```
|
||||||
|
|
||||||
Running `man -l ./target/debug/build/ripgrep-79899d0edd4129ca/out/rg.1` will
|
Or, if your version of `man` supports the `-l/--local-file` flag, then this
|
||||||
show the man page in your normal pager.
|
will suffice:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rg --generate man | man -l -
|
||||||
|
```
|
||||||
|
|
||||||
Note that the man page's documentation for options is equivalent to the output
|
Note that the man page's documentation for options is equivalent to the output
|
||||||
shown in `rg --help`. To see more condensed documentation (one line per flag),
|
shown in `rg --help`. To see more condensed documentation (one line per flag),
|
||||||
@ -86,22 +92,59 @@ The man page is also included in all
|
|||||||
Does ripgrep have support for shell auto-completion?
|
Does ripgrep have support for shell auto-completion?
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
Yes! Shell completions can be found in the
|
Yes! If you installed ripgrep through a package manager on a Unix system, then
|
||||||
[same directory as the man page](#manpage)
|
the shell completion files included in the release archive should have been
|
||||||
after building ripgrep. Zsh completions are maintained separately and committed
|
installed for you automatically. If not, you can generate completions using
|
||||||
to the repository in `complete/_rg`.
|
ripgrep's command line interface.
|
||||||
|
|
||||||
Shell completions are also included in all
|
For **bash**:
|
||||||
[ripgrep binary releases](https://github.com/BurntSushi/ripgrep/releases).
|
|
||||||
|
|
||||||
For **bash**, move `rg.bash` to
|
```
|
||||||
`$XDG_CONFIG_HOME/bash_completion` or `/etc/bash_completion.d/`.
|
$ dir="$XDG_CONFIG_HOME/bash_completion"
|
||||||
|
$ mkdir -p "$dir"
|
||||||
|
$ rg --generate complete-bash > "$dir/rg.bash"
|
||||||
|
```
|
||||||
|
|
||||||
For **fish**, move `rg.fish` to `$HOME/.config/fish/completions/`.
|
For **fish**:
|
||||||
|
|
||||||
For **zsh**, move `_rg` to one of your `$fpath` directories.
|
```
|
||||||
|
$ dir="$XDG_CONFIG_HOME/fish/completions"
|
||||||
|
$ mkdir -p "$dir"
|
||||||
|
$ rg --generate complete-fish > "$dir/rg.fish"
|
||||||
|
```
|
||||||
|
|
||||||
For **PowerShell**, add `. _rg.ps1` to your PowerShell
|
For **zsh**, the recommended approach is:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
$ dir="$HOME/.zsh-complete"
|
||||||
|
$ mkdir -p "$dir"
|
||||||
|
$ rg --generate complete-zsh > "$dir/_rg"
|
||||||
|
```
|
||||||
|
|
||||||
|
And then add `$HOME/.zsh-complete` to your `fpath` in, e.g., your
|
||||||
|
`$HOME/.zshrc` file:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
fpath=($HOME/.zsh-complete $fpath)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you'd prefer to load and generate completions at the same time, you can
|
||||||
|
add the following to your `$HOME/.zshrc` file:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
$ source <(rg --generate complete-zsh)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note though that while this approach is easier to setup, is generally slower
|
||||||
|
than the previous method, and will add more time to loading your shell prompt.
|
||||||
|
|
||||||
|
For **PowerShell**, create the completions:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rg --generate complete-powershell > _rg.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
And then add `. _rg.ps1` to your PowerShell
|
||||||
[profile](https://technet.microsoft.com/en-us/library/bb613488(v=vs.85).aspx)
|
[profile](https://technet.microsoft.com/en-us/library/bb613488(v=vs.85).aspx)
|
||||||
(note the leading period). If the `_rg.ps1` file is not on your `PATH`, do
|
(note the leading period). If the `_rg.ps1` file is not on your `PATH`, do
|
||||||
`. /path/to/_rg.ps1` instead.
|
`. /path/to/_rg.ps1` instead.
|
||||||
@ -1010,15 +1053,11 @@ tools like ack or The Silver Searcher weren't already doing.
|
|||||||
How can I donate to ripgrep or its maintainers?
|
How can I donate to ripgrep or its maintainers?
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
As of now, you can't. While I believe the various efforts that are being
|
I welcome [sponsorship](https://github.com/sponsors/BurntSushi/).
|
||||||
undertaken to help fund FOSS are extremely important, they aren't a good fit
|
|
||||||
for me. ripgrep is and I hope will remain a project of love that I develop in
|
|
||||||
my free time. As such, involving money---even in the form of donations given
|
|
||||||
without expectations---would severely change that dynamic for me personally.
|
|
||||||
|
|
||||||
Instead, I'd recommend donating to something else that is doing work that you
|
Or if you'd prefer, donating to a charitably organization that you like would
|
||||||
find meaningful. If you would like suggestions, then my favorites are:
|
also be most welcome. My favorites are:
|
||||||
|
|
||||||
* [The Internet Archive](https://archive.org/donate/)
|
* [The Internet Archive](https://archive.org/donate/)
|
||||||
* [Rails Girls](https://railsgirlssummerofcode.org/campaign/)
|
* [Rails Girls](https://railsgirlssummerofcode.org/)
|
||||||
* [Wikipedia](https://wikimediafoundation.org/support/)
|
* [Wikipedia](https://wikimediafoundation.org/support/)
|
||||||
|
32
GUIDE.md
32
GUIDE.md
@ -178,11 +178,15 @@ search. By default, when you search a directory, ripgrep will ignore all of
|
|||||||
the following:
|
the following:
|
||||||
|
|
||||||
1. Files and directories that match glob patterns in these three categories:
|
1. Files and directories that match glob patterns in these three categories:
|
||||||
1. gitignore globs (including global and repo-specific globs).
|
1. `.gitignore` globs (including global and repo-specific globs). This
|
||||||
2. `.ignore` globs, which take precedence over all gitignore globs when
|
includes `.gitignore` files in parent directories that are part of the
|
||||||
there's a conflict.
|
same `git` repository. (Unless the `--no-require-git` flag is given.)
|
||||||
3. `.rgignore` globs, which take precedence over all `.ignore` globs when
|
2. `.ignore` globs, which take precedence over all gitignore globs
|
||||||
there's a conflict.
|
when there's a conflict. This includes `.ignore` files in parent
|
||||||
|
directories.
|
||||||
|
3. `.rgignore` globs, which take precedence over all `.ignore` globs
|
||||||
|
when there's a conflict. This includes `.rgignore` files in parent
|
||||||
|
directories.
|
||||||
2. Hidden files and directories.
|
2. Hidden files and directories.
|
||||||
3. Binary files. (ripgrep considers any file with a `NUL` byte to be binary.)
|
3. Binary files. (ripgrep considers any file with a `NUL` byte to be binary.)
|
||||||
4. Symbolic links aren't followed.
|
4. Symbolic links aren't followed.
|
||||||
@ -190,7 +194,8 @@ the following:
|
|||||||
All of these things can be toggled using various flags provided by ripgrep:
|
All of these things can be toggled using various flags provided by ripgrep:
|
||||||
|
|
||||||
1. You can disable all ignore-related filtering with the `--no-ignore` flag.
|
1. You can disable all ignore-related filtering with the `--no-ignore` flag.
|
||||||
2. Hidden files and directories can be searched with the `--hidden` flag.
|
2. Hidden files and directories can be searched with the `--hidden` (`-.` for
|
||||||
|
short) flag.
|
||||||
3. Binary files can be searched via the `--text` (`-a` for short) flag.
|
3. Binary files can be searched via the `--text` (`-a` for short) flag.
|
||||||
Be careful with this flag! Binary files may emit control characters to your
|
Be careful with this flag! Binary files may emit control characters to your
|
||||||
terminal, which might cause strange behavior.
|
terminal, which might cause strange behavior.
|
||||||
@ -566,12 +571,15 @@ $ cat $HOME/.ripgreprc
|
|||||||
--type-add
|
--type-add
|
||||||
web:*.{html,css,js}*
|
web:*.{html,css,js}*
|
||||||
|
|
||||||
|
# Search hidden files / directories (e.g. dotfiles) by default
|
||||||
|
--hidden
|
||||||
|
|
||||||
# Using glob patterns to include/exclude files or folders
|
# Using glob patterns to include/exclude files or folders
|
||||||
--glob=!git/*
|
--glob=!.git/*
|
||||||
|
|
||||||
# or
|
# or
|
||||||
--glob
|
--glob
|
||||||
!git/*
|
!.git/*
|
||||||
|
|
||||||
# Set the colors.
|
# Set the colors.
|
||||||
--colors=line:none
|
--colors=line:none
|
||||||
@ -648,9 +656,9 @@ given, which is the default:
|
|||||||
they correspond to a UTF-16 BOM, then ripgrep will transcode the contents of
|
they correspond to a UTF-16 BOM, then ripgrep will transcode the contents of
|
||||||
the file from UTF-16 to UTF-8, and then execute the search on the transcoded
|
the file from UTF-16 to UTF-8, and then execute the search on the transcoded
|
||||||
version of the file. (This incurs a performance penalty since transcoding
|
version of the file. (This incurs a performance penalty since transcoding
|
||||||
is slower than regex searching.) If the file contains invalid UTF-16, then
|
is needed in addition to regex searching.) If the file contains invalid
|
||||||
the Unicode replacement codepoint is substituted in place of invalid code
|
UTF-16, then the Unicode replacement codepoint is substituted in place of
|
||||||
units.
|
invalid code units.
|
||||||
* To handle other cases, ripgrep provides a `-E/--encoding` flag, which permits
|
* To handle other cases, ripgrep provides a `-E/--encoding` flag, which permits
|
||||||
you to specify an encoding from the
|
you to specify an encoding from the
|
||||||
[Encoding Standard](https://encoding.spec.whatwg.org/#concept-encoding-get).
|
[Encoding Standard](https://encoding.spec.whatwg.org/#concept-encoding-get).
|
||||||
@ -992,6 +1000,8 @@ used options that will likely impact how you use ripgrep on a regular basis.
|
|||||||
* `-S/--smart-case`: This is similar to `--ignore-case`, but disables itself
|
* `-S/--smart-case`: This is similar to `--ignore-case`, but disables itself
|
||||||
if the pattern contains any uppercase letters. Usually this flag is put into
|
if the pattern contains any uppercase letters. Usually this flag is put into
|
||||||
alias or a config file.
|
alias or a config file.
|
||||||
|
* `-F/--fixed-strings`: Disable regular expression matching and treat the pattern
|
||||||
|
as a literal string.
|
||||||
* `-w/--word-regexp`: Require that all matches of the pattern be surrounded
|
* `-w/--word-regexp`: Require that all matches of the pattern be surrounded
|
||||||
by word boundaries. That is, given `pattern`, the `--word-regexp` flag will
|
by word boundaries. That is, given `pattern`, the `--word-regexp` flag will
|
||||||
cause ripgrep to behave as if `pattern` were actually `\b(?:pattern)\b`.
|
cause ripgrep to behave as if `pattern` were actually `\b(?:pattern)\b`.
|
||||||
|
226
README.md
226
README.md
@ -2,11 +2,11 @@ ripgrep (rg)
|
|||||||
------------
|
------------
|
||||||
ripgrep is a line-oriented search tool that recursively searches the current
|
ripgrep is a line-oriented search tool that recursively searches the current
|
||||||
directory for a regex pattern. By default, ripgrep will respect gitignore rules
|
directory for a regex pattern. By default, ripgrep will respect gitignore rules
|
||||||
and automatically skip hidden files/directories and binary files. ripgrep
|
and automatically skip hidden files/directories and binary files. (To disable
|
||||||
has first class support on Windows, macOS and Linux, with binary downloads
|
all automatic filtering by default, use `rg -uuu`.) ripgrep has first class
|
||||||
available for [every release](https://github.com/BurntSushi/ripgrep/releases).
|
support on Windows, macOS and Linux, with binary downloads available for [every
|
||||||
ripgrep is similar to other popular search tools like The Silver Searcher, ack
|
release](https://github.com/BurntSushi/ripgrep/releases). ripgrep is similar to
|
||||||
and grep.
|
other popular search tools like The Silver Searcher, ack and grep.
|
||||||
|
|
||||||
[](https://github.com/BurntSushi/ripgrep/actions)
|
[](https://github.com/BurntSushi/ripgrep/actions)
|
||||||
[](https://crates.io/crates/ripgrep)
|
[](https://crates.io/crates/ripgrep)
|
||||||
@ -42,7 +42,7 @@ This example searches the entire
|
|||||||
[Linux kernel source tree](https://github.com/BurntSushi/linux)
|
[Linux kernel source tree](https://github.com/BurntSushi/linux)
|
||||||
(after running `make defconfig && make -j8`) for `[A-Z]+_SUSPEND`, where
|
(after running `make defconfig && make -j8`) for `[A-Z]+_SUSPEND`, where
|
||||||
all matches must be words. Timings were collected on a system with an Intel
|
all matches must be words. Timings were collected on a system with an Intel
|
||||||
i7-6900K 3.2 GHz.
|
i9-12900K 5.2 GHz.
|
||||||
|
|
||||||
Please remember that a single benchmark is never enough! See my
|
Please remember that a single benchmark is never enough! See my
|
||||||
[blog post on ripgrep](https://blog.burntsushi.net/ripgrep/)
|
[blog post on ripgrep](https://blog.burntsushi.net/ripgrep/)
|
||||||
@ -50,13 +50,14 @@ for a very detailed comparison with more benchmarks and analysis.
|
|||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
| Tool | Command | Line count | Time |
|
||||||
| ---- | ------- | ---------- | ---- |
|
| ---- | ------- | ---------- | ---- |
|
||||||
| ripgrep (Unicode) | `rg -n -w '[A-Z]+_SUSPEND'` | 452 | **0.136s** |
|
| ripgrep (Unicode) | `rg -n -w '[A-Z]+_SUSPEND'` | 536 | **0.082s** (1.00x) |
|
||||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `git grep -P -n -w '[A-Z]+_SUSPEND'` | 452 | 0.348s |
|
| [hypergrep](https://github.com/p-ranav/hypergrep) | `hgrep -n -w '[A-Z]+_SUSPEND'` | 536 | 0.167s (2.04x) |
|
||||||
| [ugrep (Unicode)](https://github.com/Genivia/ugrep) | `ugrep -r --ignore-files --no-hidden -I -w '[A-Z]+_SUSPEND'` | 452 | 0.506s |
|
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `git grep -P -n -w '[A-Z]+_SUSPEND'` | 536 | 0.273s (3.34x) |
|
||||||
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 452 | 0.654s |
|
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 534 | 0.443s (5.43x) |
|
||||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 452 | 1.150s |
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r --ignore-files --no-hidden -I -w '[A-Z]+_SUSPEND'` | 536 | 0.639s (7.82x) |
|
||||||
| [ack](https://github.com/beyondgrep/ack3) | `ack -w '[A-Z]+_SUSPEND'` | 452 | 4.054s |
|
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 0.727s (8.91x) |
|
||||||
| [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 452 | 4.205s |
|
| [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 2.670s (32.70x) |
|
||||||
|
| [ack](https://github.com/beyondgrep/ack3) | `ack -w '[A-Z]+_SUSPEND'` | 2677 | 2.935s (35.94x) |
|
||||||
|
|
||||||
Here's another benchmark on the same corpus as above that disregards gitignore
|
Here's another benchmark on the same corpus as above that disregards gitignore
|
||||||
files and searches with a whitelist instead. The corpus is the same as in the
|
files and searches with a whitelist instead. The corpus is the same as in the
|
||||||
@ -65,24 +66,52 @@ doing equivalent work:
|
|||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
| Tool | Command | Line count | Time |
|
||||||
| ---- | ------- | ---------- | ---- |
|
| ---- | ------- | ---------- | ---- |
|
||||||
| ripgrep | `rg -uuu -tc -n -w '[A-Z]+_SUSPEND'` | 388 | **0.096s** |
|
| ripgrep | `rg -uuu -tc -n -w '[A-Z]+_SUSPEND'` | 447 | **0.063s** (1.00x) |
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 388 | 0.493s |
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.607s (9.62x) |
|
||||||
| [GNU grep](https://www.gnu.org/software/grep/) | `egrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 388 | 0.806s |
|
| [GNU grep](https://www.gnu.org/software/grep/) | `grep -E -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.674s (10.69x) |
|
||||||
|
|
||||||
And finally, a straight-up comparison between ripgrep, ugrep and GNU grep on a
|
Now we'll move to searching on single large file. Here is a straight-up
|
||||||
single large file cached in memory
|
comparison between ripgrep, ugrep and GNU grep on a file cached in memory
|
||||||
(~13GB, [`OpenSubtitles.raw.en.gz`](http://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/mono/OpenSubtitles.raw.en.gz)):
|
(~13GB, [`OpenSubtitles.raw.en.gz`](http://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/mono/OpenSubtitles.raw.en.gz), decompressed):
|
||||||
|
|
||||||
| Tool | Command | Line count | Time |
|
| Tool | Command | Line count | Time |
|
||||||
| ---- | ------- | ---------- | ---- |
|
| ---- | ------- | ---------- | ---- |
|
||||||
| ripgrep | `rg -w 'Sherlock [A-Z]\w+'` | 7882 | **2.769s** |
|
| ripgrep (Unicode) | `rg -w 'Sherlock [A-Z]\w+'` | 7882 | **1.042s** (1.00x) |
|
||||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w 'Sherlock [A-Z]\w+'` | 7882 | 6.802s |
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w 'Sherlock [A-Z]\w+'` | 7882 | 1.339s (1.28x) |
|
||||||
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 egrep -w 'Sherlock [A-Z]\w+'` | 7882 | 9.027s |
|
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 egrep -w 'Sherlock [A-Z]\w+'` | 7882 | 6.577s (6.31x) |
|
||||||
|
|
||||||
In the above benchmark, passing the `-n` flag (for showing line numbers)
|
In the above benchmark, passing the `-n` flag (for showing line numbers)
|
||||||
increases the times to `3.423s` for ripgrep and `13.031s` for GNU grep. ugrep
|
increases the times to `1.664s` for ripgrep and `9.484s` for GNU grep. ugrep
|
||||||
times are unaffected by the presence or absence of `-n`.
|
times are unaffected by the presence or absence of `-n`.
|
||||||
|
|
||||||
|
Beware of performance cliffs though:
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep (Unicode) | `rg -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | **1.053s** (1.00x) |
|
||||||
|
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 6.234s (5.92x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 28.973s (27.51x) |
|
||||||
|
|
||||||
|
And performance can drop precipitously across the board when searching big
|
||||||
|
files for patterns without any opportunities for literal optimizations:
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep | `rg '[A-Za-z]{30}'` | 6749 | **15.569s** (1.00x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -E '[A-Za-z]{30}'` | 6749 | 21.857s (1.40x) |
|
||||||
|
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep -E '[A-Za-z]{30}'` | 6749 | 32.409s (2.08x) |
|
||||||
|
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E '[A-Za-z]{30}'` | 6795 | 8m30s (32.74x) |
|
||||||
|
|
||||||
|
Finally, high match counts also tend to both tank performance and smooth
|
||||||
|
out the differences between tools (because performance is dominated by how
|
||||||
|
quickly one can handle a match and not the algorithm used to detect the match,
|
||||||
|
generally speaking):
|
||||||
|
|
||||||
|
| Tool | Command | Line count | Time |
|
||||||
|
| ---- | ------- | ---------- | ---- |
|
||||||
|
| ripgrep | `rg the` | 83499915 | **6.948s** (1.00x) |
|
||||||
|
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep the` | 83499915 | 11.721s (1.69x) |
|
||||||
|
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep the` | 83499915 | 15.217s (2.19x) |
|
||||||
|
|
||||||
### Why should I use ripgrep?
|
### Why should I use ripgrep?
|
||||||
|
|
||||||
@ -90,16 +119,16 @@ times are unaffected by the presence or absence of `-n`.
|
|||||||
because it contains most of their features and is generally faster. (See
|
because it contains most of their features and is generally faster. (See
|
||||||
[the FAQ](FAQ.md#posix4ever) for more details on whether ripgrep can truly
|
[the FAQ](FAQ.md#posix4ever) for more details on whether ripgrep can truly
|
||||||
replace grep.)
|
replace grep.)
|
||||||
* Like other tools specialized to code search, ripgrep defaults to recursive
|
* Like other tools specialized to code search, ripgrep defaults to
|
||||||
directory search and won't search files ignored by your
|
[recursive search](GUIDE.md#recursive-search) and does [automatic
|
||||||
`.gitignore`/`.ignore`/`.rgignore` files. It also ignores hidden and binary
|
filtering](GUIDE.md#automatic-filtering). Namely, ripgrep won't search files
|
||||||
files by default. ripgrep also implements full support for `.gitignore`,
|
ignored by your `.gitignore`/`.ignore`/`.rgignore` files, it won't search
|
||||||
whereas there are many bugs related to that functionality in other code
|
hidden files and it won't search binary files. Automatic filtering can be
|
||||||
search tools claiming to provide the same functionality.
|
disabled with `rg -uuu`.
|
||||||
* ripgrep can search specific types of files. For example, `rg -tpy foo`
|
* ripgrep can [search specific types of files](GUIDE.md#manual-filtering-file-types).
|
||||||
limits your search to Python files and `rg -Tjs foo` excludes JavaScript
|
For example, `rg -tpy foo` limits your search to Python files and `rg -Tjs
|
||||||
files from your search. ripgrep can be taught about new file types with
|
foo` excludes JavaScript files from your search. ripgrep can be taught about
|
||||||
custom matching rules.
|
new file types with custom matching rules.
|
||||||
* ripgrep supports many features found in `grep`, such as showing the context
|
* ripgrep supports many features found in `grep`, such as showing the context
|
||||||
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
|
||||||
@ -109,17 +138,21 @@ times are unaffected by the presence or absence of `-n`.
|
|||||||
backreferences in your patterns, which are not supported in ripgrep's default
|
backreferences in your patterns, which are not supported in ripgrep's default
|
||||||
regex engine. PCRE2 support can be enabled with `-P/--pcre2` (use PCRE2
|
regex engine. PCRE2 support can be enabled with `-P/--pcre2` (use PCRE2
|
||||||
always) or `--auto-hybrid-regex` (use PCRE2 only if needed). An alternative
|
always) or `--auto-hybrid-regex` (use PCRE2 only if needed). An alternative
|
||||||
syntax is provided via the `--engine (default|pcre2|auto-hybrid)` option.
|
syntax is provided via the `--engine (default|pcre2|auto)` option.
|
||||||
* ripgrep supports searching files in text encodings other than UTF-8, such
|
* ripgrep has [rudimentary support for replacements](GUIDE.md#replacements),
|
||||||
as UTF-16, latin-1, GBK, EUC-JP, Shift_JIS and more. (Some support for
|
which permit rewriting output based on what was matched.
|
||||||
automatically detecting UTF-16 is provided. Other text encodings must be
|
* ripgrep supports [searching files in text encodings](GUIDE.md#file-encoding)
|
||||||
specifically specified with the `-E/--encoding` flag.)
|
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.)
|
||||||
* ripgrep supports searching files compressed in a common format (brotli,
|
* ripgrep supports searching files compressed in a common format (brotli,
|
||||||
bzip2, gzip, lz4, lzma, xz, or zstandard) with the `-z/--search-zip` flag.
|
bzip2, gzip, lz4, lzma, xz, or zstandard) with the `-z/--search-zip` flag.
|
||||||
* ripgrep supports
|
* ripgrep supports
|
||||||
[arbitrary input preprocessing filters](GUIDE.md#preprocessor)
|
[arbitrary input preprocessing filters](GUIDE.md#preprocessor)
|
||||||
which could be PDF text extraction, less supported decompression, decrypting,
|
which could be PDF text extraction, less supported decompression, decrypting,
|
||||||
automatic encoding detection and so on.
|
automatic encoding detection and so on.
|
||||||
|
* ripgrep can be configured via a
|
||||||
|
[configuration file](GUIDE.md#configuration-file).
|
||||||
|
|
||||||
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.
|
||||||
@ -187,6 +220,16 @@ configuration files, passthru, support for searching compressed files,
|
|||||||
multiline search and opt-in fancy regex support via PCRE2.
|
multiline search and opt-in fancy regex support via PCRE2.
|
||||||
|
|
||||||
|
|
||||||
|
### Playground
|
||||||
|
|
||||||
|
If you'd like to try ripgrep before installing, there's an unofficial
|
||||||
|
[playground](https://codapi.org/ripgrep/) and an [interactive
|
||||||
|
tutorial](https://codapi.org/try/ripgrep/).
|
||||||
|
|
||||||
|
If you have any questions about these, please open an issue in the [tutorial
|
||||||
|
repo](https://github.com/nalgeon/tryxinyminutes).
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
The binary name for ripgrep is `rg`.
|
The binary name for ripgrep is `rg`.
|
||||||
@ -224,17 +267,25 @@ If you're a **Windows Scoop** user, then you can install ripgrep from the
|
|||||||
$ scoop install ripgrep
|
$ scoop install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you're a **Windows Winget** user, then you can install ripgrep from the
|
||||||
|
[winget-pkgs](https://github.com/microsoft/winget-pkgs/tree/master/manifests/b/BurntSushi/ripgrep)
|
||||||
|
repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ winget install BurntSushi.ripgrep.MSVC
|
||||||
|
```
|
||||||
|
|
||||||
If you're an **Arch Linux** user, then you can install ripgrep from the official repos:
|
If you're an **Arch Linux** user, then you can install ripgrep from the official repos:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ pacman -S ripgrep
|
$ sudo pacman -S ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Gentoo** user, you can install ripgrep from the
|
If you're a **Gentoo** user, you can install ripgrep from the
|
||||||
[official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
|
[official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ emerge sys-apps/ripgrep
|
$ sudo emerge sys-apps/ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Fedora** user, you can install ripgrep from official
|
If you're a **Fedora** user, you can install ripgrep from official
|
||||||
@ -255,6 +306,7 @@ If you're a **RHEL/CentOS 7/8** user, you can install ripgrep from
|
|||||||
[copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
[copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
||||||
|
|
||||||
```
|
```
|
||||||
|
$ sudo yum install -y yum-utils
|
||||||
$ sudo yum-config-manager --add-repo=https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/repo/epel-7/carlwgeorge-ripgrep-epel-7.repo
|
$ sudo yum-config-manager --add-repo=https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/repo/epel-7/carlwgeorge-ripgrep-epel-7.repo
|
||||||
$ sudo yum install ripgrep
|
$ sudo yum install ripgrep
|
||||||
```
|
```
|
||||||
@ -264,7 +316,19 @@ If you're a **Nix** user, you can install ripgrep from
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ nix-env --install ripgrep
|
$ nix-env --install ripgrep
|
||||||
$ # (Or using the attribute name, which is also ripgrep.)
|
```
|
||||||
|
|
||||||
|
If you're a **Flox** user, you can install ripgrep as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ flox install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Guix** user, you can install ripgrep from the official
|
||||||
|
package collection:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ guix install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
||||||
@ -272,12 +336,14 @@ then ripgrep can be installed using a binary `.deb` file provided in each
|
|||||||
[ripgrep release](https://github.com/BurntSushi/ripgrep/releases).
|
[ripgrep release](https://github.com/BurntSushi/ripgrep/releases).
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/12.1.1/ripgrep_12.1.1_amd64.deb
|
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/14.1.0/ripgrep_14.1.0-1_amd64.deb
|
||||||
$ sudo dpkg -i ripgrep_12.1.1_amd64.deb
|
$ sudo dpkg -i ripgrep_14.1.0-1_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
If you run Debian Buster (currently Debian stable) or Debian sid, ripgrep is
|
If you run Debian stable, ripgrep is [officially maintained by
|
||||||
[officially maintained by Debian](https://tracker.debian.org/pkg/rust-ripgrep).
|
Debian](https://tracker.debian.org/pkg/rust-ripgrep), although its version may
|
||||||
|
be older than the `deb` package available in the previous step.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo apt-get install ripgrep
|
$ sudo apt-get install ripgrep
|
||||||
```
|
```
|
||||||
@ -295,11 +361,18 @@ seem to work right and generate a number of very strange bug reports that I
|
|||||||
don't know how to fix and don't have the time to fix. Therefore, it is no
|
don't know how to fix and don't have the time to fix. Therefore, it is no
|
||||||
longer a recommended installation option.)
|
longer a recommended installation option.)
|
||||||
|
|
||||||
|
If you're an **ALT** user, you can install ripgrep from the
|
||||||
|
[official repo](https://packages.altlinux.org/en/search?name=ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
If you're a **FreeBSD** user, then you can install ripgrep from the
|
If you're a **FreeBSD** user, then you can install ripgrep from the
|
||||||
[official ports](https://www.freshports.org/textproc/ripgrep/):
|
[official ports](https://www.freshports.org/textproc/ripgrep/):
|
||||||
|
|
||||||
```
|
```
|
||||||
# pkg install ripgrep
|
$ sudo pkg install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're an **OpenBSD** user, then you can install ripgrep from the
|
If you're an **OpenBSD** user, then you can install ripgrep from the
|
||||||
@ -313,26 +386,33 @@ If you're a **NetBSD** user, then you can install ripgrep from
|
|||||||
[pkgsrc](https://pkgsrc.se/textproc/ripgrep):
|
[pkgsrc](https://pkgsrc.se/textproc/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
# pkgin install ripgrep
|
$ sudo pkgin install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Haiku x86_64** user, then you can install ripgrep from the
|
If you're a **Haiku x86_64** user, then you can install ripgrep from the
|
||||||
[official ports](https://github.com/haikuports/haikuports/tree/master/sys-apps/ripgrep):
|
[official ports](https://github.com/haikuports/haikuports/tree/master/sys-apps/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ pkgman install ripgrep
|
$ sudo pkgman install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Haiku x86_gcc2** user, then you can install ripgrep from the
|
If you're a **Haiku x86_gcc2** user, then you can install ripgrep from the
|
||||||
same port as Haiku x86_64 using the x86 secondary architecture build:
|
same port as Haiku x86_64 using the x86 secondary architecture build:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ pkgman install ripgrep_x86
|
$ sudo pkgman install ripgrep_x86
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're a **Void Linux** user, then you can install ripgrep from the
|
||||||
|
[official repository](https://voidlinux.org/packages/?arch=x86_64&q=ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo xbps-install -Syv 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 the minimum supported version of Rust for ripgrep is **1.34.0**,
|
* Note that the minimum supported version of Rust for ripgrep is **1.72.0**,
|
||||||
although ripgrep may work with older versions.
|
although ripgrep may work with older versions.
|
||||||
* Note that the binary may be bigger than expected because it contains debug
|
* Note that the binary may be bigger than expected because it contains debug
|
||||||
symbols. This is intentional. To remove debug symbols and therefore reduce
|
symbols. This is intentional. To remove debug symbols and therefore reduce
|
||||||
@ -342,12 +422,20 @@ If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
|||||||
$ cargo install ripgrep
|
$ cargo install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively, one can use [`cargo
|
||||||
|
binstall`](https://github.com/cargo-bins/cargo-binstall) to install a ripgrep
|
||||||
|
binary directly from GitHub:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo binstall ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
ripgrep is written in Rust, so you'll need to grab a
|
ripgrep is written in Rust, so you'll need to grab a
|
||||||
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
||||||
ripgrep compiles with Rust 1.34.0 (stable) or newer. In general, ripgrep tracks
|
ripgrep compiles with Rust 1.72.0 (stable) or newer. In general, ripgrep tracks
|
||||||
the latest stable release of the Rust compiler.
|
the latest stable release of the Rust compiler.
|
||||||
|
|
||||||
To build ripgrep:
|
To build ripgrep:
|
||||||
@ -360,18 +448,13 @@ $ ./target/release/rg --version
|
|||||||
0.1.3
|
0.1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
If you have a Rust nightly compiler and a recent Intel CPU, then you can enable
|
**NOTE:** In the past, ripgrep supported a `simd-accel` Cargo feature when
|
||||||
additional optional SIMD acceleration like so:
|
using a Rust nightly compiler. This only benefited UTF-16 transcoding.
|
||||||
|
Since it required unstable features, this build mode was prone to breakage.
|
||||||
```
|
Because of that, support for it has been removed. If you want SIMD
|
||||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features 'simd-accel'
|
optimizations for UTF-16 transcoding, then you'll have to petition the
|
||||||
```
|
[`encoding_rs`](https://github.com/hsivonen/encoding_rs) project to use stable
|
||||||
|
APIs.
|
||||||
The `simd-accel` feature enables SIMD support in certain ripgrep dependencies
|
|
||||||
(responsible for transcoding). They are not necessary to get SIMD optimizations
|
|
||||||
for search; those are enabled automatically. Hopefully, some day, the
|
|
||||||
`simd-accel` feature will similarly become unnecessary. **WARNING:** Currently,
|
|
||||||
enabling this option can increase compilation times dramatically.
|
|
||||||
|
|
||||||
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
||||||
`pcre2` feature:
|
`pcre2` feature:
|
||||||
@ -380,9 +463,6 @@ Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
|||||||
$ cargo build --release --features 'pcre2'
|
$ cargo build --release --features 'pcre2'
|
||||||
```
|
```
|
||||||
|
|
||||||
(Tip: use `--features 'pcre2 simd-accel'` to also include compile time SIMD
|
|
||||||
optimizations, which will only work with a nightly compiler.)
|
|
||||||
|
|
||||||
Enabling the PCRE2 feature works with a stable Rust compiler and will
|
Enabling the PCRE2 feature works with a stable Rust compiler and will
|
||||||
attempt to automatically find and link with your system's PCRE2 library via
|
attempt to automatically find and link with your system's PCRE2 library via
|
||||||
`pkg-config`. If one doesn't exist, then ripgrep will build PCRE2 from source
|
`pkg-config`. If one doesn't exist, then ripgrep will build PCRE2 from source
|
||||||
@ -419,12 +499,20 @@ $ cargo test --all
|
|||||||
from the repository root.
|
from the repository root.
|
||||||
|
|
||||||
|
|
||||||
|
### Related tools
|
||||||
|
|
||||||
|
* [delta](https://github.com/dandavison/delta) is a syntax highlighting
|
||||||
|
pager that supports the `rg --json` output format. So all you need to do to
|
||||||
|
make it work is `rg --json pattern | delta`. See [delta's manual section on
|
||||||
|
grep](https://dandavison.github.io/delta/grep.html) for more details.
|
||||||
|
|
||||||
|
|
||||||
### Vulnerability reporting
|
### Vulnerability reporting
|
||||||
|
|
||||||
For reporting a security vulnerability, please
|
For reporting a security vulnerability, please
|
||||||
[contact Andrew Gallant](https://blog.burntsushi.net/about/),
|
[contact Andrew Gallant](https://blog.burntsushi.net/about/).
|
||||||
which has my email address and PGP public key if you wish to send an encrypted
|
The contact page has my email address and PGP public key if you wish to send an
|
||||||
message.
|
encrypted message.
|
||||||
|
|
||||||
|
|
||||||
### Translations
|
### Translations
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
Release Checklist
|
# Release Checklist
|
||||||
-----------------
|
|
||||||
* Ensure local `master` is up to date with respect to `origin/master`.
|
* Ensure local `master` is up to date with respect to `origin/master`.
|
||||||
* Run `cargo update` and review dependency updates. Commit updated
|
* Run `cargo update` and review dependency updates. Commit updated
|
||||||
`Cargo.lock`.
|
`Cargo.lock`.
|
||||||
* Run `cargo outdated` and review semver incompatible updates. Unless there is
|
* Run `cargo outdated` and review semver incompatible updates. Unless there is
|
||||||
a strong motivation otherwise, review and update every dependency. Also
|
a strong motivation otherwise, review and update every dependency. Also
|
||||||
run `--aggressive`, but don't update to crates that are still in beta.
|
run `--aggressive`, but don't update to crates that are still in beta.
|
||||||
|
* Update date in `crates/core/flags/doc/template.rg.1`.
|
||||||
* Review changes for every crate in `crates` since the last ripgrep release.
|
* Review changes for every crate in `crates` since the last ripgrep release.
|
||||||
If the set of changes is non-empty, issue a new release for that crate. Check
|
If the set of changes is non-empty, issue a new release for that crate. Check
|
||||||
crates in the following order. After updating a crate, ensure minimal
|
crates in the following order. After updating a crate, ensure minimal
|
||||||
@ -26,6 +27,12 @@ Release Checklist
|
|||||||
`cargo update -p ripgrep` so that the `Cargo.lock` is updated. Commit the
|
`cargo update -p ripgrep` so that the `Cargo.lock` is updated. Commit the
|
||||||
changes and create a new signed tag. Alternatively, use
|
changes and create a new signed tag. Alternatively, use
|
||||||
`cargo-up --no-push --no-release Cargo.toml {VERSION}` to automate this.
|
`cargo-up --no-push --no-release Cargo.toml {VERSION}` to automate this.
|
||||||
|
* Run `cargo package` and ensure it succeeds.
|
||||||
|
* Push changes to GitHub, NOT including the tag. (But do not publish a new
|
||||||
|
version of ripgrep to crates.io yet.)
|
||||||
|
* Once CI for `master` finishes successfully, push the version tag. (Trying to
|
||||||
|
do this in one step seems to result in GitHub Actions not seeing the tag
|
||||||
|
push and thus not running the release workflow.)
|
||||||
* Wait for CI to finish creating the release. If the release build fails, then
|
* Wait for CI to finish creating the release. If the release build fails, then
|
||||||
delete the tag from GitHub, make fixes, re-tag, delete the release and push.
|
delete the tag from GitHub, make fixes, re-tag, delete the release and push.
|
||||||
* Copy the relevant section of the CHANGELOG to the tagged release notes.
|
* Copy the relevant section of the CHANGELOG to the tagged release notes.
|
||||||
@ -34,8 +41,8 @@ Release Checklist
|
|||||||
> tool that recursively searches the current directory for a regex pattern.
|
> tool that recursively searches the current directory for a regex pattern.
|
||||||
> By default, ripgrep will respect gitignore rules and automatically skip
|
> By default, ripgrep will respect gitignore rules and automatically skip
|
||||||
> hidden files/directories and binary files.
|
> hidden files/directories and binary files.
|
||||||
* Run `ci/build-deb` locally and manually upload the deb package to the
|
* Run `git checkout {VERSION} && ci/build-and-publish-m2 {VERSION}` on a macOS
|
||||||
release.
|
system with Apple silicon.
|
||||||
* Run `cargo publish`.
|
* Run `cargo publish`.
|
||||||
* Run `ci/sha256-releases {VERSION} >> pkg/brew/ripgrep-bin.rb`. Then edit
|
* Run `ci/sha256-releases {VERSION} >> pkg/brew/ripgrep-bin.rb`. Then edit
|
||||||
`pkg/brew/ripgrep-bin.rb` to update the version number and sha256 hashes.
|
`pkg/brew/ripgrep-bin.rb` to update the version number and sha256 hashes.
|
||||||
@ -47,5 +54,6 @@ Release Checklist
|
|||||||
Unreleased changes. Release notes have not yet been written.
|
Unreleased changes. Release notes have not yet been written.
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that
|
Note that [`cargo-up` can be found in BurntSushi's dotfiles][dotfiles].
|
||||||
[`cargo-up` can be found in BurntSushi's dotfiles](https://github.com/BurntSushi/dotfiles/blob/master/bin/cargo-up).
|
|
||||||
|
[dotfiles]: https://github.com/BurntSushi/dotfiles/blob/master/bin/cargo-up
|
||||||
|
@ -26,15 +26,13 @@ SUBTITLES_DIR = 'subtitles'
|
|||||||
SUBTITLES_EN_NAME = 'en.txt'
|
SUBTITLES_EN_NAME = 'en.txt'
|
||||||
SUBTITLES_EN_NAME_SAMPLE = 'en.sample.txt'
|
SUBTITLES_EN_NAME_SAMPLE = 'en.sample.txt'
|
||||||
SUBTITLES_EN_NAME_GZ = '%s.gz' % SUBTITLES_EN_NAME
|
SUBTITLES_EN_NAME_GZ = '%s.gz' % SUBTITLES_EN_NAME
|
||||||
# SUBTITLES_EN_URL = 'http://opus.lingfil.uu.se/OpenSubtitles2016/mono/OpenSubtitles2016.raw.en.gz' # noqa
|
|
||||||
SUBTITLES_EN_URL = 'https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/en.txt.gz' # noqa
|
SUBTITLES_EN_URL = 'https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/en.txt.gz' # noqa
|
||||||
SUBTITLES_RU_NAME = 'ru.txt'
|
SUBTITLES_RU_NAME = 'ru.txt'
|
||||||
SUBTITLES_RU_NAME_GZ = '%s.gz' % SUBTITLES_RU_NAME
|
SUBTITLES_RU_NAME_GZ = '%s.gz' % SUBTITLES_RU_NAME
|
||||||
# SUBTITLES_RU_URL = 'http://opus.lingfil.uu.se/OpenSubtitles2016/mono/OpenSubtitles2016.raw.ru.gz' # noqa
|
|
||||||
SUBTITLES_RU_URL = 'https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/ru.txt.gz' # noqa
|
SUBTITLES_RU_URL = 'https://object.pouta.csc.fi/OPUS-OpenSubtitles/v2016/mono/ru.txt.gz' # noqa
|
||||||
|
|
||||||
LINUX_DIR = 'linux'
|
LINUX_DIR = 'linux'
|
||||||
LINUX_CLONE = 'git://github.com/BurntSushi/linux'
|
LINUX_CLONE = 'https://github.com/BurntSushi/linux'
|
||||||
|
|
||||||
# Grep takes locale settings from the environment. There is a *substantial*
|
# Grep takes locale settings from the environment. There is a *substantial*
|
||||||
# performance impact for enabling Unicode, so we need to handle this explicitly
|
# performance impact for enabling Unicode, so we need to handle this explicitly
|
||||||
@ -546,7 +544,11 @@ def bench_subtitles_ru_literal(suite_dir):
|
|||||||
Command('rg (lines)', ['rg', '-n', pat, ru]),
|
Command('rg (lines)', ['rg', '-n', pat, ru]),
|
||||||
Command('ag (lines)', ['ag', '-s', pat, ru]),
|
Command('ag (lines)', ['ag', '-s', pat, ru]),
|
||||||
Command('grep (lines)', ['grep', '-n', pat, ru], env=GREP_ASCII),
|
Command('grep (lines)', ['grep', '-n', pat, ru], env=GREP_ASCII),
|
||||||
Command('ugrep (lines)', ['ugrep', '-n', pat, ru])
|
# ugrep incorrectly identifies this corpus as binary, but it is
|
||||||
|
# entirely valid UTF-8. So we tell ugrep to always treat the corpus
|
||||||
|
# as text even though this technically gives it an edge over other
|
||||||
|
# tools. (It no longer needs to check for binary data.)
|
||||||
|
Command('ugrep (lines)', ['ugrep', '-a', '-n', pat, ru])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@ -564,7 +566,8 @@ def bench_subtitles_ru_literal_casei(suite_dir):
|
|||||||
Command('grep (ASCII)', ['grep', '-E', '-i', pat, ru], env=GREP_ASCII),
|
Command('grep (ASCII)', ['grep', '-E', '-i', pat, ru], env=GREP_ASCII),
|
||||||
Command('rg (lines)', ['rg', '-n', '-i', pat, ru]),
|
Command('rg (lines)', ['rg', '-n', '-i', pat, ru]),
|
||||||
Command('ag (lines) (ASCII)', ['ag', '-i', pat, ru]),
|
Command('ag (lines) (ASCII)', ['ag', '-i', pat, ru]),
|
||||||
Command('ugrep (lines) (ASCII)', ['ugrep', '-n', '-i', pat, ru])
|
# See bench_subtitles_ru_literal for why we use '-a' here.
|
||||||
|
Command('ugrep (lines) (ASCII)', ['ugrep', '-a', '-n', '-i', pat, ru])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@ -588,7 +591,8 @@ def bench_subtitles_ru_literal_word(suite_dir):
|
|||||||
Command('grep (ASCII)', [
|
Command('grep (ASCII)', [
|
||||||
'grep', '-nw', pat, ru,
|
'grep', '-nw', pat, ru,
|
||||||
], env=GREP_ASCII),
|
], env=GREP_ASCII),
|
||||||
Command('ugrep (ASCII)', ['ugrep', '-nw', pat, ru]),
|
# See bench_subtitles_ru_literal for why we use '-a' here.
|
||||||
|
Command('ugrep (ASCII)', ['ugrep', '-anw', pat, ru]),
|
||||||
Command('rg', ['rg', '-nw', pat, ru]),
|
Command('rg', ['rg', '-nw', pat, ru]),
|
||||||
Command('grep', ['grep', '-nw', pat, ru], env=GREP_UNICODE),
|
Command('grep', ['grep', '-nw', pat, ru], env=GREP_UNICODE),
|
||||||
])
|
])
|
||||||
@ -612,7 +616,8 @@ def bench_subtitles_ru_alternate(suite_dir):
|
|||||||
Command('rg (lines)', ['rg', '-n', pat, ru]),
|
Command('rg (lines)', ['rg', '-n', pat, ru]),
|
||||||
Command('ag (lines)', ['ag', '-s', pat, ru]),
|
Command('ag (lines)', ['ag', '-s', pat, ru]),
|
||||||
Command('grep (lines)', ['grep', '-E', '-n', pat, ru], env=GREP_ASCII),
|
Command('grep (lines)', ['grep', '-E', '-n', pat, ru], env=GREP_ASCII),
|
||||||
Command('ugrep (lines)', ['ugrep', '-n', pat, ru]),
|
# See bench_subtitles_ru_literal for why we use '-a' here.
|
||||||
|
Command('ugrep (lines)', ['ugrep', '-an', pat, ru]),
|
||||||
Command('rg', ['rg', pat, ru]),
|
Command('rg', ['rg', pat, ru]),
|
||||||
Command('grep', ['grep', '-E', pat, ru], env=GREP_ASCII),
|
Command('grep', ['grep', '-E', pat, ru], env=GREP_ASCII),
|
||||||
])
|
])
|
||||||
@ -637,7 +642,8 @@ def bench_subtitles_ru_alternate_casei(suite_dir):
|
|||||||
Command('grep (ASCII)', [
|
Command('grep (ASCII)', [
|
||||||
'grep', '-E', '-ni', pat, ru,
|
'grep', '-E', '-ni', pat, ru,
|
||||||
], env=GREP_ASCII),
|
], env=GREP_ASCII),
|
||||||
Command('ugrep (ASCII)', ['ugrep', '-n', '-i', pat, ru]),
|
# See bench_subtitles_ru_literal for why we use '-a' here.
|
||||||
|
Command('ugrep (ASCII)', ['ugrep', '-ani', pat, ru]),
|
||||||
Command('rg', ['rg', '-n', '-i', pat, ru]),
|
Command('rg', ['rg', '-n', '-i', pat, ru]),
|
||||||
Command('grep', ['grep', '-E', '-ni', pat, ru], env=GREP_UNICODE),
|
Command('grep', ['grep', '-E', '-ni', pat, ru], env=GREP_UNICODE),
|
||||||
])
|
])
|
||||||
@ -654,10 +660,11 @@ def bench_subtitles_ru_surrounding_words(suite_dir):
|
|||||||
return Benchmark(pattern=pat, commands=[
|
return Benchmark(pattern=pat, commands=[
|
||||||
Command('rg', ['rg', '-n', pat, ru]),
|
Command('rg', ['rg', '-n', pat, ru]),
|
||||||
Command('grep', ['grep', '-E', '-n', pat, ru], env=GREP_UNICODE),
|
Command('grep', ['grep', '-E', '-n', pat, ru], env=GREP_UNICODE),
|
||||||
Command('ugrep', ['ugrep', '-n', pat, ru]),
|
Command('ugrep', ['ugrep', '-an', pat, ru]),
|
||||||
Command('ag (ASCII)', ['ag', '-s', pat, ru]),
|
Command('ag (ASCII)', ['ag', '-s', pat, ru]),
|
||||||
Command('grep (ASCII)', ['grep', '-E', '-n', pat, ru], env=GREP_ASCII),
|
Command('grep (ASCII)', ['grep', '-E', '-n', pat, ru], env=GREP_ASCII),
|
||||||
Command('ugrep (ASCII)', ['ugrep', '-n', '-U', pat, ru]),
|
# See bench_subtitles_ru_literal for why we use '-a' here.
|
||||||
|
Command('ugrep (ASCII)', ['ugrep', '-a', '-n', '-U', pat, ru]),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@ -676,11 +683,13 @@ def bench_subtitles_ru_no_literal(suite_dir):
|
|||||||
|
|
||||||
return Benchmark(pattern=pat, commands=[
|
return Benchmark(pattern=pat, commands=[
|
||||||
Command('rg', ['rg', '-n', pat, ru]),
|
Command('rg', ['rg', '-n', pat, ru]),
|
||||||
Command('ugrep', ['ugrep', '-n', pat, ru]),
|
# See bench_subtitles_ru_literal for why we use '-a' here.
|
||||||
|
Command('ugrep', ['ugrep', '-an', pat, ru]),
|
||||||
Command('rg (ASCII)', ['rg', '-n', '(?-u)' + pat, ru]),
|
Command('rg (ASCII)', ['rg', '-n', '(?-u)' + pat, ru]),
|
||||||
Command('ag (ASCII)', ['ag', '-s', pat, ru]),
|
Command('ag (ASCII)', ['ag', '-s', pat, ru]),
|
||||||
Command('grep (ASCII)', ['grep', '-E', '-n', pat, ru], env=GREP_ASCII),
|
Command('grep (ASCII)', ['grep', '-E', '-n', pat, ru], env=GREP_ASCII),
|
||||||
Command('ugrep (ASCII)', ['ugrep', '-n', '-U', pat, ru])
|
# See bench_subtitles_ru_literal for why we use '-a' here.
|
||||||
|
Command('ugrep (ASCII)', ['ugrep', '-anU', pat, ru])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
38
benchsuite/runs/2022-12-16-archlinux-duff/README.md
Normal file
38
benchsuite/runs/2022-12-16-archlinux-duff/README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
This directory contains updated benchmarks as of 2022-12-16. They were captured
|
||||||
|
via the benchsuite script at `benchsuite/benchsuite` from the root of this
|
||||||
|
repository. The command that was run:
|
||||||
|
|
||||||
|
$ ./benchsuite \
|
||||||
|
--dir /dev/shm/benchsuite \
|
||||||
|
--raw runs/2022-12-16-archlinux-duff/raw.csv \
|
||||||
|
| tee runs/2022-12-16-archlinux-duff/summary
|
||||||
|
|
||||||
|
The versions of each tool are as follows:
|
||||||
|
|
||||||
|
$ rg --version
|
||||||
|
ripgrep 13.0.0 (rev 87c4a2b4b1)
|
||||||
|
-SIMD -AVX (compiled)
|
||||||
|
+SIMD +AVX (runtime)
|
||||||
|
|
||||||
|
$ grep -V
|
||||||
|
grep (GNU grep) 3.8
|
||||||
|
|
||||||
|
$ ag -V
|
||||||
|
ag version 2.2.0
|
||||||
|
|
||||||
|
Features:
|
||||||
|
+jit +lzma +zlib
|
||||||
|
|
||||||
|
$ git --version
|
||||||
|
git version 2.39.0
|
||||||
|
|
||||||
|
$ ugrep --version
|
||||||
|
ugrep 3.9.2 x86_64-pc-linux-gnu +avx2 +pcre2jit +zlib +bzip2 +lzma +lz4 +zstd
|
||||||
|
License BSD-3-Clause: <https://opensource.org/licenses/BSD-3-Clause>
|
||||||
|
Written by Robert van Engelen and others: <https://github.com/Genivia/ugrep>
|
||||||
|
|
||||||
|
The version of ripgrep used was compiled from source on commit 7f23cd63:
|
||||||
|
|
||||||
|
$ cargo build --release --features 'pcre2'
|
||||||
|
|
||||||
|
This was run on a machine with an Intel i9-12900K with 128GB of memory.
|
400
benchsuite/runs/2022-12-16-archlinux-duff/raw.csv
Normal file
400
benchsuite/runs/2022-12-16-archlinux-duff/raw.csv
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
benchmark,warmup_iter,iter,name,command,duration,lines,env
|
||||||
|
linux_literal_default,1,3,rg,rg PM_RESUME,0.08678817749023438,39,
|
||||||
|
linux_literal_default,1,3,rg,rg PM_RESUME,0.08307123184204102,39,
|
||||||
|
linux_literal_default,1,3,rg,rg PM_RESUME,0.08347964286804199,39,
|
||||||
|
linux_literal_default,1,3,ag,ag PM_RESUME,0.2955434322357178,39,
|
||||||
|
linux_literal_default,1,3,ag,ag PM_RESUME,0.2954287528991699,39,
|
||||||
|
linux_literal_default,1,3,ag,ag PM_RESUME,0.2938194274902344,39,
|
||||||
|
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.23198556900024414,39,LC_ALL=en_US.UTF-8
|
||||||
|
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.22356963157653809,39,LC_ALL=en_US.UTF-8
|
||||||
|
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.2189793586730957,39,LC_ALL=en_US.UTF-8
|
||||||
|
linux_literal_default,1,3,ugrep,ugrep -r PM_RESUME ./,0.10710000991821289,39,
|
||||||
|
linux_literal_default,1,3,ugrep,ugrep -r PM_RESUME ./,0.10364222526550293,39,
|
||||||
|
linux_literal_default,1,3,ugrep,ugrep -r PM_RESUME ./,0.1052248477935791,39,
|
||||||
|
linux_literal_default,1,3,grep,grep -r PM_RESUME ./,0.9994468688964844,39,LC_ALL=en_US.UTF-8
|
||||||
|
linux_literal_default,1,3,grep,grep -r PM_RESUME ./,0.9939279556274414,39,LC_ALL=en_US.UTF-8
|
||||||
|
linux_literal_default,1,3,grep,grep -r PM_RESUME ./,0.9957931041717529,39,LC_ALL=en_US.UTF-8
|
||||||
|
linux_literal,1,3,rg,rg -n PM_RESUME,0.08603358268737793,39,
|
||||||
|
linux_literal,1,3,rg,rg -n PM_RESUME,0.0837090015411377,39,
|
||||||
|
linux_literal,1,3,rg,rg -n PM_RESUME,0.08435535430908203,39,
|
||||||
|
linux_literal,1,3,rg (mmap),rg -n --mmap PM_RESUME,0.3215503692626953,39,
|
||||||
|
linux_literal,1,3,rg (mmap),rg -n --mmap PM_RESUME,0.32426929473876953,39,
|
||||||
|
linux_literal,1,3,rg (mmap),rg -n --mmap PM_RESUME,0.3215982913970947,39,
|
||||||
|
linux_literal,1,3,ag (mmap),ag -s PM_RESUME,0.2894856929779053,39,
|
||||||
|
linux_literal,1,3,ag (mmap),ag -s PM_RESUME,0.2892603874206543,39,
|
||||||
|
linux_literal,1,3,ag (mmap),ag -s PM_RESUME,0.29217028617858887,39,
|
||||||
|
linux_literal,1,3,git grep,git grep -I -n PM_RESUME,0.206068754196167,39,LC_ALL=C
|
||||||
|
linux_literal,1,3,git grep,git grep -I -n PM_RESUME,0.2218036651611328,39,LC_ALL=C
|
||||||
|
linux_literal,1,3,git grep,git grep -I -n PM_RESUME,0.20590710639953613,39,LC_ALL=C
|
||||||
|
linux_literal,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n PM_RESUME ./,0.18692874908447266,39,
|
||||||
|
linux_literal,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n PM_RESUME ./,0.19518327713012695,39,
|
||||||
|
linux_literal,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n PM_RESUME ./,0.18577361106872559,39,
|
||||||
|
linux_literal_casei,1,3,rg,rg -n -i PM_RESUME,0.08709383010864258,536,
|
||||||
|
linux_literal_casei,1,3,rg,rg -n -i PM_RESUME,0.08861064910888672,536,
|
||||||
|
linux_literal_casei,1,3,rg,rg -n -i PM_RESUME,0.08769798278808594,536,
|
||||||
|
linux_literal_casei,1,3,rg (mmap),rg -n -i --mmap PM_RESUME,0.3218965530395508,536,
|
||||||
|
linux_literal_casei,1,3,rg (mmap),rg -n -i --mmap PM_RESUME,0.30869364738464355,536,
|
||||||
|
linux_literal_casei,1,3,rg (mmap),rg -n -i --mmap PM_RESUME,0.31044936180114746,536,
|
||||||
|
linux_literal_casei,1,3,ag (mmap),ag -i PM_RESUME,0.2989068031311035,536,
|
||||||
|
linux_literal_casei,1,3,ag (mmap),ag -i PM_RESUME,0.2996039390563965,536,
|
||||||
|
linux_literal_casei,1,3,ag (mmap),ag -i PM_RESUME,0.29817700386047363,536,
|
||||||
|
linux_literal_casei,1,3,git grep,git grep -I -n -i PM_RESUME,0.2122786045074463,536,LC_ALL=C
|
||||||
|
linux_literal_casei,1,3,git grep,git grep -I -n -i PM_RESUME,0.20763754844665527,536,LC_ALL=C
|
||||||
|
linux_literal_casei,1,3,git grep,git grep -I -n -i PM_RESUME,0.220794677734375,536,LC_ALL=C
|
||||||
|
linux_literal_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i PM_RESUME ./,0.17305850982666016,536,
|
||||||
|
linux_literal_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i PM_RESUME ./,0.1745915412902832,536,
|
||||||
|
linux_literal_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i PM_RESUME ./,0.17526865005493164,536,
|
||||||
|
linux_re_literal_suffix,1,3,rg,rg -n [A-Z]+_RESUME,0.08527851104736328,2160,
|
||||||
|
linux_re_literal_suffix,1,3,rg,rg -n [A-Z]+_RESUME,0.08487534523010254,2160,
|
||||||
|
linux_re_literal_suffix,1,3,rg,rg -n [A-Z]+_RESUME,0.0848684310913086,2160,
|
||||||
|
linux_re_literal_suffix,1,3,ag,ag -s [A-Z]+_RESUME,0.37945985794067383,2160,
|
||||||
|
linux_re_literal_suffix,1,3,ag,ag -s [A-Z]+_RESUME,0.36303210258483887,2160,
|
||||||
|
linux_re_literal_suffix,1,3,ag,ag -s [A-Z]+_RESUME,0.36359691619873047,2160,
|
||||||
|
linux_re_literal_suffix,1,3,git grep,git grep -E -I -n [A-Z]+_RESUME,0.9589834213256836,2160,LC_ALL=C
|
||||||
|
linux_re_literal_suffix,1,3,git grep,git grep -E -I -n [A-Z]+_RESUME,0.9206984043121338,2160,LC_ALL=C
|
||||||
|
linux_re_literal_suffix,1,3,git grep,git grep -E -I -n [A-Z]+_RESUME,0.8642933368682861,2160,LC_ALL=C
|
||||||
|
linux_re_literal_suffix,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n [A-Z]+_RESUME ./,0.40503501892089844,2160,
|
||||||
|
linux_re_literal_suffix,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n [A-Z]+_RESUME ./,0.4531714916229248,2160,
|
||||||
|
linux_re_literal_suffix,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n [A-Z]+_RESUME ./,0.4397866725921631,2160,
|
||||||
|
linux_word,1,3,rg,rg -n -w PM_RESUME,0.08639907836914062,9,
|
||||||
|
linux_word,1,3,rg,rg -n -w PM_RESUME,0.08583569526672363,9,
|
||||||
|
linux_word,1,3,rg,rg -n -w PM_RESUME,0.08414363861083984,9,
|
||||||
|
linux_word,1,3,ag,ag -s -w PM_RESUME,0.2853865623474121,9,
|
||||||
|
linux_word,1,3,ag,ag -s -w PM_RESUME,0.2871377468109131,9,
|
||||||
|
linux_word,1,3,ag,ag -s -w PM_RESUME,0.28753662109375,9,
|
||||||
|
linux_word,1,3,git grep,git grep -E -I -n -w PM_RESUME,0.20428204536437988,9,LC_ALL=C
|
||||||
|
linux_word,1,3,git grep,git grep -E -I -n -w PM_RESUME,0.20490717887878418,9,LC_ALL=C
|
||||||
|
linux_word,1,3,git grep,git grep -E -I -n -w PM_RESUME,0.20840072631835938,9,LC_ALL=C
|
||||||
|
linux_word,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -w PM_RESUME ./,0.18790841102600098,9,
|
||||||
|
linux_word,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -w PM_RESUME ./,0.18659543991088867,9,
|
||||||
|
linux_word,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -w PM_RESUME ./,0.19104933738708496,9,
|
||||||
|
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.19976496696472168,105,
|
||||||
|
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.20618367195129395,105,
|
||||||
|
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.19702935218811035,105,
|
||||||
|
linux_unicode_greek,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \p{Greek} ./,0.17758727073669434,105,
|
||||||
|
linux_unicode_greek,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \p{Greek} ./,0.17793798446655273,105,
|
||||||
|
linux_unicode_greek,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \p{Greek} ./,0.1872577667236328,105,
|
||||||
|
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.19808244705200195,245,
|
||||||
|
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.1979837417602539,245,
|
||||||
|
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.1984400749206543,245,
|
||||||
|
linux_unicode_greek_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i \p{Greek} ./,0.1819148063659668,105,
|
||||||
|
linux_unicode_greek_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i \p{Greek} ./,0.17530512809753418,105,
|
||||||
|
linux_unicode_greek_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i \p{Greek} ./,0.17999005317687988,105,
|
||||||
|
linux_unicode_word,1,3,rg,rg -n \wAh,0.08527827262878418,247,
|
||||||
|
linux_unicode_word,1,3,rg,rg -n \wAh,0.08541679382324219,247,
|
||||||
|
linux_unicode_word,1,3,rg,rg -n \wAh,0.08553218841552734,247,
|
||||||
|
linux_unicode_word,1,3,rg (ASCII),rg -n (?-u)\wAh,0.08484745025634766,233,
|
||||||
|
linux_unicode_word,1,3,rg (ASCII),rg -n (?-u)\wAh,0.08466482162475586,233,
|
||||||
|
linux_unicode_word,1,3,rg (ASCII),rg -n (?-u)\wAh,0.08487439155578613,233,
|
||||||
|
linux_unicode_word,1,3,ag (ASCII),ag -s \wAh,0.3061795234680176,233,
|
||||||
|
linux_unicode_word,1,3,ag (ASCII),ag -s \wAh,0.2993617057800293,233,
|
||||||
|
linux_unicode_word,1,3,ag (ASCII),ag -s \wAh,0.29722046852111816,233,
|
||||||
|
linux_unicode_word,1,3,git grep,git grep -E -I -n \wAh,4.257144451141357,247,LC_ALL=en_US.UTF-8
|
||||||
|
linux_unicode_word,1,3,git grep,git grep -E -I -n \wAh,3.852163076400757,247,LC_ALL=en_US.UTF-8
|
||||||
|
linux_unicode_word,1,3,git grep,git grep -E -I -n \wAh,3.8293941020965576,247,LC_ALL=en_US.UTF-8
|
||||||
|
linux_unicode_word,1,3,git grep (ASCII),git grep -E -I -n \wAh,1.647632122039795,233,LC_ALL=C
|
||||||
|
linux_unicode_word,1,3,git grep (ASCII),git grep -E -I -n \wAh,1.6269629001617432,233,LC_ALL=C
|
||||||
|
linux_unicode_word,1,3,git grep (ASCII),git grep -E -I -n \wAh,1.5847914218902588,233,LC_ALL=C
|
||||||
|
linux_unicode_word,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \wAh ./,0.1802208423614502,247,
|
||||||
|
linux_unicode_word,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \wAh ./,0.17564702033996582,247,
|
||||||
|
linux_unicode_word,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \wAh ./,0.1746981143951416,247,
|
||||||
|
linux_unicode_word,1,3,ugrep (ASCII),ugrep -r --ignore-files --no-hidden -I -n -U \wAh ./,0.1799161434173584,233,
|
||||||
|
linux_unicode_word,1,3,ugrep (ASCII),ugrep -r --ignore-files --no-hidden -I -n -U \wAh ./,0.18733000755310059,233,
|
||||||
|
linux_unicode_word,1,3,ugrep (ASCII),ugrep -r --ignore-files --no-hidden -I -n -U \wAh ./,0.18859529495239258,233,
|
||||||
|
linux_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.26203155517578125,721,
|
||||||
|
linux_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.2615540027618408,721,
|
||||||
|
linux_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.2730247974395752,721,
|
||||||
|
linux_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.19902300834655762,720,
|
||||||
|
linux_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.20034146308898926,720,
|
||||||
|
linux_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.20192813873291016,720,
|
||||||
|
linux_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.8269081115722656,1134,
|
||||||
|
linux_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.8393104076385498,1134,
|
||||||
|
linux_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.8293666839599609,1134,
|
||||||
|
linux_no_literal,1,3,git grep,git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},7.334395408630371,721,LC_ALL=en_US.UTF-8
|
||||||
|
linux_no_literal,1,3,git grep,git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},7.338796854019165,721,LC_ALL=en_US.UTF-8
|
||||||
|
linux_no_literal,1,3,git grep,git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},7.36545991897583,721,LC_ALL=en_US.UTF-8
|
||||||
|
linux_no_literal,1,3,git grep (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},2.1588926315307617,720,LC_ALL=C
|
||||||
|
linux_no_literal,1,3,git grep (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},2.132209062576294,720,LC_ALL=C
|
||||||
|
linux_no_literal,1,3,git grep (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},2.1407439708709717,720,LC_ALL=C
|
||||||
|
linux_no_literal,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} ./,3.410162925720215,723,
|
||||||
|
linux_no_literal,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} ./,3.405057668685913,723,
|
||||||
|
linux_no_literal,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} ./,3.3945884704589844,723,
|
||||||
|
linux_no_literal,1,3,ugrep (ASCII),ugrep -r --ignore-files --no-hidden -I -n -U \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} ./,0.23865604400634766,722,
|
||||||
|
linux_no_literal,1,3,ugrep (ASCII),ugrep -r --ignore-files --no-hidden -I -n -U \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} ./,0.23371148109436035,722,
|
||||||
|
linux_no_literal,1,3,ugrep (ASCII),ugrep -r --ignore-files --no-hidden -I -n -U \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} ./,0.2343149185180664,722,
|
||||||
|
linux_alternates,1,3,rg,rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.08691263198852539,140,
|
||||||
|
linux_alternates,1,3,rg,rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.08707070350646973,140,
|
||||||
|
linux_alternates,1,3,rg,rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.08713960647583008,140,
|
||||||
|
linux_alternates,1,3,ag,ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.32947278022766113,140,
|
||||||
|
linux_alternates,1,3,ag,ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.33203840255737305,140,
|
||||||
|
linux_alternates,1,3,ag,ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.3292670249938965,140,
|
||||||
|
linux_alternates,1,3,git grep,git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.4576725959777832,140,LC_ALL=C
|
||||||
|
linux_alternates,1,3,git grep,git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.41936421394348145,140,LC_ALL=C
|
||||||
|
linux_alternates,1,3,git grep,git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.3639688491821289,140,LC_ALL=C
|
||||||
|
linux_alternates,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT ./,0.17806458473205566,140,
|
||||||
|
linux_alternates,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT ./,0.18224716186523438,140,
|
||||||
|
linux_alternates,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT ./,0.17795038223266602,140,
|
||||||
|
linux_alternates_casei,1,3,rg,rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.12421393394470215,241,
|
||||||
|
linux_alternates_casei,1,3,rg,rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.12235784530639648,241,
|
||||||
|
linux_alternates_casei,1,3,rg,rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.12151455879211426,241,
|
||||||
|
linux_alternates_casei,1,3,ag,ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.529585599899292,241,
|
||||||
|
linux_alternates_casei,1,3,ag,ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5305526256561279,241,
|
||||||
|
linux_alternates_casei,1,3,ag,ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5311264991760254,241,
|
||||||
|
linux_alternates_casei,1,3,git grep,git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.7589735984802246,241,LC_ALL=C
|
||||||
|
linux_alternates_casei,1,3,git grep,git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.7852108478546143,241,LC_ALL=C
|
||||||
|
linux_alternates_casei,1,3,git grep,git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.8308050632476807,241,LC_ALL=C
|
||||||
|
linux_alternates_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT ./,0.17955923080444336,241,
|
||||||
|
linux_alternates_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT ./,0.1745290756225586,241,
|
||||||
|
linux_alternates_casei,1,3,ugrep,ugrep -r --ignore-files --no-hidden -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT ./,0.1773686408996582,241,
|
||||||
|
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.1213979721069336,830,
|
||||||
|
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.1213991641998291,830,
|
||||||
|
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.12620782852172852,830,
|
||||||
|
subtitles_en_literal,1,3,rg (no mmap),rg --no-mmap Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18207263946533203,830,
|
||||||
|
subtitles_en_literal,1,3,rg (no mmap),rg --no-mmap Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.17281484603881836,830,
|
||||||
|
subtitles_en_literal,1,3,rg (no mmap),rg --no-mmap Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.17368507385253906,830,
|
||||||
|
subtitles_en_literal,1,3,grep,grep Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.560560941696167,830,LC_ALL=C
|
||||||
|
subtitles_en_literal,1,3,grep,grep Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.563499927520752,830,LC_ALL=C
|
||||||
|
subtitles_en_literal,1,3,grep,grep Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.5916609764099121,830,LC_ALL=C
|
||||||
|
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.19600844383239746,830,
|
||||||
|
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18436980247497559,830,
|
||||||
|
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18594050407409668,830,
|
||||||
|
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.871025562286377,830,
|
||||||
|
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.8636960983276367,830,
|
||||||
|
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.8680994510650635,830,
|
||||||
|
subtitles_en_literal,1,3,grep (lines),grep -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.9978001117706299,830,LC_ALL=C
|
||||||
|
subtitles_en_literal,1,3,grep (lines),grep -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.9385361671447754,830,LC_ALL=C
|
||||||
|
subtitles_en_literal,1,3,grep (lines),grep -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.0036489963531494,830,LC_ALL=C
|
||||||
|
subtitles_en_literal,1,3,ugrep (lines),ugrep -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18918490409851074,830,
|
||||||
|
subtitles_en_literal,1,3,ugrep (lines),ugrep -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.1769108772277832,830,
|
||||||
|
subtitles_en_literal,1,3,ugrep (lines),ugrep -n Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18808293342590332,830,
|
||||||
|
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.21876287460327148,871,
|
||||||
|
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.2044692039489746,871,
|
||||||
|
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.2184743881225586,871,
|
||||||
|
subtitles_en_literal_casei,1,3,grep,grep -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,2.224027156829834,871,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_literal_casei,1,3,grep,grep -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,2.223188877105713,871,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_literal_casei,1,3,grep,grep -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,2.223966598510742,871,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.671149492263794,871,LC_ALL=C
|
||||||
|
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.6705749034881592,871,LC_ALL=C
|
||||||
|
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.6700258255004883,871,LC_ALL=C
|
||||||
|
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.2624058723449707,871,
|
||||||
|
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.25513339042663574,871,
|
||||||
|
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.26088857650756836,871,
|
||||||
|
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.9144322872161865,871,
|
||||||
|
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.866628885269165,871,
|
||||||
|
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.9098389148712158,871,
|
||||||
|
subtitles_en_literal_casei,1,3,ugrep (lines),ugrep -n -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.7860472202301025,871,
|
||||||
|
subtitles_en_literal_casei,1,3,ugrep (lines),ugrep -n -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.7858343124389648,871,
|
||||||
|
subtitles_en_literal_casei,1,3,ugrep (lines),ugrep -n -i Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.782252311706543,871,
|
||||||
|
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /dev/shm/benchsuite/subtitles/en.sample.txt,0.18424677848815918,830,
|
||||||
|
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /dev/shm/benchsuite/subtitles/en.sample.txt,0.19610810279846191,830,
|
||||||
|
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /dev/shm/benchsuite/subtitles/en.sample.txt,0.18711471557617188,830,
|
||||||
|
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.8301315307617188,830,
|
||||||
|
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.8689801692962646,830,
|
||||||
|
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.8279321193695068,830,
|
||||||
|
subtitles_en_literal_word,1,3,grep (ASCII),grep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.0036842823028564,830,LC_ALL=C
|
||||||
|
subtitles_en_literal_word,1,3,grep (ASCII),grep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.002833604812622,830,LC_ALL=C
|
||||||
|
subtitles_en_literal_word,1,3,grep (ASCII),grep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.9236147403717041,830,LC_ALL=C
|
||||||
|
subtitles_en_literal_word,1,3,ugrep (ASCII),ugrep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.17717313766479492,830,
|
||||||
|
subtitles_en_literal_word,1,3,ugrep (ASCII),ugrep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18994617462158203,830,
|
||||||
|
subtitles_en_literal_word,1,3,ugrep (ASCII),ugrep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.17972850799560547,830,
|
||||||
|
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18804550170898438,830,
|
||||||
|
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.18867778778076172,830,
|
||||||
|
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.19913530349731445,830,
|
||||||
|
subtitles_en_literal_word,1,3,grep,grep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.0044364929199219,830,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_literal_word,1,3,grep,grep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,1.0040032863616943,830,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_literal_word,1,3,grep,grep -nw Sherlock Holmes /dev/shm/benchsuite/subtitles/en.sample.txt,0.9627983570098877,830,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.24848055839538574,1094,
|
||||||
|
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.24738383293151855,1094,
|
||||||
|
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.24789118766784668,1094,
|
||||||
|
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,2.668708562850952,1094,
|
||||||
|
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,2.57511305809021,1094,
|
||||||
|
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,2.6714110374450684,1094,
|
||||||
|
subtitles_en_alternate,1,3,grep (lines),grep -E -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,2.0586187839508057,1094,LC_ALL=C
|
||||||
|
subtitles_en_alternate,1,3,grep (lines),grep -E -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,2.0227150917053223,1094,LC_ALL=C
|
||||||
|
subtitles_en_alternate,1,3,grep (lines),grep -E -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,2.075378179550171,1094,LC_ALL=C
|
||||||
|
subtitles_en_alternate,1,3,ugrep (lines),ugrep -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.7863781452178955,1094,
|
||||||
|
subtitles_en_alternate,1,3,ugrep (lines),ugrep -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.7874250411987305,1094,
|
||||||
|
subtitles_en_alternate,1,3,ugrep (lines),ugrep -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.7867889404296875,1094,
|
||||||
|
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.18195557594299316,1094,
|
||||||
|
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.18239641189575195,1094,
|
||||||
|
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.1625690460205078,1094,
|
||||||
|
subtitles_en_alternate,1,3,grep,grep -E Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,1.6601614952087402,1094,LC_ALL=C
|
||||||
|
subtitles_en_alternate,1,3,grep,grep -E Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,1.6617567539215088,1094,LC_ALL=C
|
||||||
|
subtitles_en_alternate,1,3,grep,grep -E Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,1.6584677696228027,1094,LC_ALL=C
|
||||||
|
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,4.0028722286224365,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,3.991217851638794,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,4.00272274017334,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ni Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,3.549154758453369,1136,LC_ALL=C
|
||||||
|
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ni Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,3.5468921661376953,1136,LC_ALL=C
|
||||||
|
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ni Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,3.5873491764068604,1136,LC_ALL=C
|
||||||
|
subtitles_en_alternate_casei,1,3,ugrep (ASCII),ugrep -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.7872169017791748,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,ugrep (ASCII),ugrep -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.784674882888794,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,ugrep (ASCII),ugrep -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.7882401943206787,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.4785435199737549,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.4940922260284424,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,0.4774627685546875,1136,
|
||||||
|
subtitles_en_alternate_casei,1,3,grep,grep -E -ni Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,3.5677175521850586,1136,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_alternate_casei,1,3,grep,grep -E -ni Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,3.603273391723633,1136,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_en_alternate_casei,1,3,grep,grep -E -ni Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /dev/shm/benchsuite/subtitles/en.sample.txt,3.5834741592407227,1136,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.20238041877746582,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.2031264305114746,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.20475172996520996,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep,grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0288453102111816,278,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep,grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.044802188873291,278,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep,grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0432109832763672,278,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep,ugrep -an \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,43.00765633583069,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep,ugrep -an \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,42.832849740982056,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep,ugrep -an \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,42.915205240249634,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.083683967590332,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0841526985168457,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0850934982299805,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0116353034973145,,LC_ALL=C
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.9868073463439941,,LC_ALL=C
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0224814414978027,,LC_ALL=C
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep (ASCII),ugrep -a -n -U \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.8892502784729004,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep (ASCII),ugrep -a -n -U \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.8910088539123535,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep (ASCII),ugrep -a -n -U \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.8897674083709717,,
|
||||||
|
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,2.11850643157959,22,
|
||||||
|
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,2.1359670162200928,22,
|
||||||
|
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,2.103114128112793,22,
|
||||||
|
subtitles_en_no_literal,1,3,ugrep,ugrep -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,13.050881385803223,22,
|
||||||
|
subtitles_en_no_literal,1,3,ugrep,ugrep -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,13.050772190093994,22,
|
||||||
|
subtitles_en_no_literal,1,3,ugrep,ugrep -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,13.05719804763794,22,
|
||||||
|
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,1.9961926937103271,22,
|
||||||
|
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,2.019721508026123,22,
|
||||||
|
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,1.9965126514434814,22,
|
||||||
|
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,6.849602222442627,302,
|
||||||
|
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,6.813834190368652,302,
|
||||||
|
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,6.8263633251190186,302,
|
||||||
|
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,4.42924165725708,22,LC_ALL=C
|
||||||
|
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,4.378557205200195,22,LC_ALL=C
|
||||||
|
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,4.376646518707275,22,LC_ALL=C
|
||||||
|
subtitles_en_no_literal,1,3,ugrep (ASCII),ugrep -n -U \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,3.5110037326812744,22,
|
||||||
|
subtitles_en_no_literal,1,3,ugrep (ASCII),ugrep -n -U \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,3.5137360095977783,22,
|
||||||
|
subtitles_en_no_literal,1,3,ugrep (ASCII),ugrep -n -U \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/en.sample.txt,3.5051844120025635,22,
|
||||||
|
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.13207745552062988,583,
|
||||||
|
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.13084721565246582,583,
|
||||||
|
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.13469862937927246,583,
|
||||||
|
subtitles_ru_literal,1,3,rg (no mmap),rg --no-mmap Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.18022370338439941,583,
|
||||||
|
subtitles_ru_literal,1,3,rg (no mmap),rg --no-mmap Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.1801767349243164,583,
|
||||||
|
subtitles_ru_literal,1,3,rg (no mmap),rg --no-mmap Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.17995166778564453,583,
|
||||||
|
subtitles_ru_literal,1,3,grep,grep Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.5151040554046631,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal,1,3,grep,grep Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.5154542922973633,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal,1,3,grep,grep Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.49927639961242676,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.19464492797851562,583,
|
||||||
|
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.18920588493347168,583,
|
||||||
|
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.19465351104736328,583,
|
||||||
|
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,1.9595966339111328,583,
|
||||||
|
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,2.0014493465423584,583,
|
||||||
|
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,1.9567768573760986,583,
|
||||||
|
subtitles_ru_literal,1,3,grep (lines),grep -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.8119180202484131,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal,1,3,grep (lines),grep -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.8111097812652588,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal,1,3,grep (lines),grep -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.8006868362426758,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal,1,3,ugrep (lines),ugrep -a -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.70003342628479,583,
|
||||||
|
subtitles_ru_literal,1,3,ugrep (lines),ugrep -a -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.650275468826294,583,
|
||||||
|
subtitles_ru_literal,1,3,ugrep (lines),ugrep -a -n Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.689772367477417,583,
|
||||||
|
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.267578125,604,
|
||||||
|
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.2665982246398926,604,
|
||||||
|
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.26861572265625,604,
|
||||||
|
subtitles_ru_literal_casei,1,3,grep,grep -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,4.764627456665039,604,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_literal_casei,1,3,grep,grep -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,4.767015695571899,604,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_literal_casei,1,3,grep,grep -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,4.7688889503479,604,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.5046737194061279,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.5139875411987305,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.4993159770965576,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.33438658714294434,604,
|
||||||
|
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.3398289680480957,604,
|
||||||
|
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.3298227787017822,604,
|
||||||
|
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.4468214511871338,,
|
||||||
|
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.44559574127197266,,
|
||||||
|
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.47882938385009766,,
|
||||||
|
subtitles_ru_literal_casei,1,3,ugrep (lines) (ASCII),ugrep -a -n -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.7039575576782227,583,
|
||||||
|
subtitles_ru_literal_casei,1,3,ugrep (lines) (ASCII),ugrep -a -n -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.6490752696990967,583,
|
||||||
|
subtitles_ru_literal_casei,1,3,ugrep (lines) (ASCII),ugrep -a -n -i Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.8081104755401611,583,
|
||||||
|
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:^|\W)Шерлок Холмс(?-u:$|\W) /dev/shm/benchsuite/subtitles/ru.txt,0.20162224769592285,583,
|
||||||
|
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:^|\W)Шерлок Холмс(?-u:$|\W) /dev/shm/benchsuite/subtitles/ru.txt,0.18215250968933105,583,
|
||||||
|
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:^|\W)Шерлок Холмс(?-u:$|\W) /dev/shm/benchsuite/subtitles/ru.txt,0.20087671279907227,583,
|
||||||
|
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.48624587059020996,,
|
||||||
|
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.5212516784667969,,
|
||||||
|
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.520557165145874,,
|
||||||
|
subtitles_ru_literal_word,1,3,grep (ASCII),grep -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.8108196258544922,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal_word,1,3,grep (ASCII),grep -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.8121066093444824,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal_word,1,3,grep (ASCII),grep -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.7784581184387207,583,LC_ALL=C
|
||||||
|
subtitles_ru_literal_word,1,3,ugrep (ASCII),ugrep -anw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.7469344139099121,583,
|
||||||
|
subtitles_ru_literal_word,1,3,ugrep (ASCII),ugrep -anw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.6838233470916748,583,
|
||||||
|
subtitles_ru_literal_word,1,3,ugrep (ASCII),ugrep -anw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.6921679973602295,583,
|
||||||
|
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.19918251037597656,579,
|
||||||
|
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.2046656608581543,579,
|
||||||
|
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.1984848976135254,579,
|
||||||
|
subtitles_ru_literal_word,1,3,grep,grep -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.794173002243042,579,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_literal_word,1,3,grep,grep -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.7715346813201904,579,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_literal_word,1,3,grep,grep -nw Шерлок Холмс /dev/shm/benchsuite/subtitles/ru.txt,0.8116705417633057,579,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,0.6730976104736328,691,
|
||||||
|
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,0.7020411491394043,691,
|
||||||
|
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,0.6693949699401855,691,
|
||||||
|
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,2.7100515365600586,691,
|
||||||
|
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,2.7458419799804688,691,
|
||||||
|
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,2.7115116119384766,691,
|
||||||
|
subtitles_ru_alternate,1,3,grep (lines),grep -E -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.703738451004028,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate,1,3,grep (lines),grep -E -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.715883731842041,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate,1,3,grep (lines),grep -E -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.712724924087524,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate,1,3,ugrep (lines),ugrep -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,8.276995420455933,691,
|
||||||
|
subtitles_ru_alternate,1,3,ugrep (lines),ugrep -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,8.304608345031738,691,
|
||||||
|
subtitles_ru_alternate,1,3,ugrep (lines),ugrep -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,8.322760820388794,691,
|
||||||
|
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,0.6119842529296875,691,
|
||||||
|
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,0.6368775367736816,691,
|
||||||
|
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,0.6258070468902588,691,
|
||||||
|
subtitles_ru_alternate,1,3,grep,grep -E Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.4300291538238525,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate,1,3,grep,grep -E Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.418199300765991,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate,1,3,grep,grep -E Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.425868511199951,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,2.7216460704803467,691,
|
||||||
|
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,2.7108607292175293,691,
|
||||||
|
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,2.747138500213623,691,
|
||||||
|
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ni Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.711230039596558,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ni Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.709407329559326,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ni Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.714034557342529,691,LC_ALL=C
|
||||||
|
subtitles_ru_alternate_casei,1,3,ugrep (ASCII),ugrep -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,8.305904626846313,691,
|
||||||
|
subtitles_ru_alternate_casei,1,3,ugrep (ASCII),ugrep -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,8.307406187057495,691,
|
||||||
|
subtitles_ru_alternate_casei,1,3,ugrep (ASCII),ugrep -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,8.288233995437622,691,
|
||||||
|
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,3.673624277114868,735,
|
||||||
|
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,3.6759188175201416,735,
|
||||||
|
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,3.66877818107605,735,
|
||||||
|
subtitles_ru_alternate_casei,1,3,grep,grep -E -ni Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.366282224655151,735,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_alternate_casei,1,3,grep,grep -E -ni Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.370524883270264,735,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_alternate_casei,1,3,grep,grep -E -ni Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /dev/shm/benchsuite/subtitles/ru.txt,5.342163324356079,735,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.20331382751464844,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.2034592628479004,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.20407724380493164,278,
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep,grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0436389446258545,278,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep,grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0388383865356445,278,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep,grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0446207523345947,278,LC_ALL=en_US.UTF-8
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep,ugrep -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.29245424270629883,1,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep,ugrep -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.29168128967285156,1,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep,ugrep -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.29593825340270996,1,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.085604190826416,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.083526372909546,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.1223819255828857,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.9905192852020264,,LC_ALL=C
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0222513675689697,,LC_ALL=C
|
||||||
|
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -n \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,1.0216262340545654,,LC_ALL=C
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep (ASCII),ugrep -a -n -U \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.8875806331634521,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep (ASCII),ugrep -a -n -U \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.8861405849456787,,
|
||||||
|
subtitles_ru_surrounding_words,1,3,ugrep (ASCII),ugrep -a -n -U \w+\s+Холмс\s+\w+ /dev/shm/benchsuite/subtitles/ru.txt,0.8898241519927979,,
|
||||||
|
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,2.237398147583008,41,
|
||||||
|
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,2.253706693649292,41,
|
||||||
|
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,2.2161178588867188,41,
|
||||||
|
subtitles_ru_no_literal,1,3,ugrep,ugrep -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,28.85959553718567,41,
|
||||||
|
subtitles_ru_no_literal,1,3,ugrep,ugrep -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,28.666419982910156,41,
|
||||||
|
subtitles_ru_no_literal,1,3,ugrep,ugrep -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,28.90555214881897,41,
|
||||||
|
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,2.051813840866089,,
|
||||||
|
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,2.026675224304199,,
|
||||||
|
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,2.027498245239258,,
|
||||||
|
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,1.0998010635375977,,
|
||||||
|
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,1.0900018215179443,,
|
||||||
|
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,1.0901548862457275,,
|
||||||
|
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,1.0691263675689697,,LC_ALL=C
|
||||||
|
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,1.0875153541564941,,LC_ALL=C
|
||||||
|
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,1.0997354984283447,,LC_ALL=C
|
||||||
|
subtitles_ru_no_literal,1,3,ugrep (ASCII),ugrep -anU \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,0.8329172134399414,,
|
||||||
|
subtitles_ru_no_literal,1,3,ugrep (ASCII),ugrep -anU \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,0.8292679786682129,,
|
||||||
|
subtitles_ru_no_literal,1,3,ugrep (ASCII),ugrep -anU \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /dev/shm/benchsuite/subtitles/ru.txt,0.8326950073242188,,
|
|
208
benchsuite/runs/2022-12-16-archlinux-duff/summary
Normal file
208
benchsuite/runs/2022-12-16-archlinux-duff/summary
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
linux_literal_default (pattern: PM_RESUME)
|
||||||
|
------------------------------------------
|
||||||
|
rg* 0.084 +/- 0.002 (lines: 39)*
|
||||||
|
ag 0.295 +/- 0.001 (lines: 39)
|
||||||
|
git grep 0.225 +/- 0.007 (lines: 39)
|
||||||
|
ugrep 0.105 +/- 0.002 (lines: 39)
|
||||||
|
grep 0.996 +/- 0.003 (lines: 39)
|
||||||
|
|
||||||
|
linux_literal (pattern: PM_RESUME)
|
||||||
|
----------------------------------
|
||||||
|
rg* 0.085 +/- 0.001 (lines: 39)*
|
||||||
|
rg (mmap) 0.322 +/- 0.002 (lines: 39)
|
||||||
|
ag (mmap) 0.290 +/- 0.002 (lines: 39)
|
||||||
|
git grep 0.211 +/- 0.009 (lines: 39)
|
||||||
|
ugrep 0.189 +/- 0.005 (lines: 39)
|
||||||
|
|
||||||
|
linux_literal_casei (pattern: PM_RESUME)
|
||||||
|
----------------------------------------
|
||||||
|
rg* 0.088 +/- 0.001 (lines: 536)*
|
||||||
|
rg (mmap) 0.314 +/- 0.007 (lines: 536)
|
||||||
|
ag (mmap) 0.299 +/- 0.001 (lines: 536)
|
||||||
|
git grep 0.214 +/- 0.007 (lines: 536)
|
||||||
|
ugrep 0.174 +/- 0.001 (lines: 536)
|
||||||
|
|
||||||
|
linux_re_literal_suffix (pattern: [A-Z]+_RESUME)
|
||||||
|
------------------------------------------------
|
||||||
|
rg* 0.085 +/- 0.000 (lines: 2160)*
|
||||||
|
ag 0.369 +/- 0.009 (lines: 2160)
|
||||||
|
git grep 0.915 +/- 0.048 (lines: 2160)
|
||||||
|
ugrep 0.433 +/- 0.025 (lines: 2160)
|
||||||
|
|
||||||
|
linux_word (pattern: PM_RESUME)
|
||||||
|
-------------------------------
|
||||||
|
rg* 0.085 +/- 0.001 (lines: 9)*
|
||||||
|
ag 0.287 +/- 0.001 (lines: 9)
|
||||||
|
git grep 0.206 +/- 0.002 (lines: 9)
|
||||||
|
ugrep 0.189 +/- 0.002 (lines: 9)
|
||||||
|
|
||||||
|
linux_unicode_greek (pattern: \p{Greek})
|
||||||
|
----------------------------------------
|
||||||
|
rg 0.201 +/- 0.005 (lines: 105)
|
||||||
|
ugrep* 0.181 +/- 0.005 (lines: 105)*
|
||||||
|
|
||||||
|
linux_unicode_greek_casei (pattern: \p{Greek})
|
||||||
|
----------------------------------------------
|
||||||
|
rg 0.198 +/- 0.000 (lines: 245)
|
||||||
|
ugrep* 0.179 +/- 0.003 (lines: 105)*
|
||||||
|
|
||||||
|
linux_unicode_word (pattern: \wAh)
|
||||||
|
----------------------------------
|
||||||
|
rg 0.085 +/- 0.000 (lines: 247)
|
||||||
|
rg (ASCII)* 0.085 +/- 0.000 (lines: 233)*
|
||||||
|
ag (ASCII) 0.301 +/- 0.005 (lines: 233)
|
||||||
|
git grep 3.980 +/- 0.241 (lines: 247)
|
||||||
|
git grep (ASCII) 1.620 +/- 0.032 (lines: 233)
|
||||||
|
ugrep 0.177 +/- 0.003 (lines: 247)
|
||||||
|
ugrep (ASCII) 0.185 +/- 0.005 (lines: 233)
|
||||||
|
|
||||||
|
linux_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
rg 0.266 +/- 0.006 (lines: 721)
|
||||||
|
rg (ASCII)* 0.200 +/- 0.001 (lines: 720)*
|
||||||
|
ag (ASCII) 0.832 +/- 0.007 (lines: 1134)
|
||||||
|
git grep 7.346 +/- 0.017 (lines: 721)
|
||||||
|
git grep (ASCII) 2.144 +/- 0.014 (lines: 720)
|
||||||
|
ugrep 3.403 +/- 0.008 (lines: 723)
|
||||||
|
ugrep (ASCII) 0.236 +/- 0.003 (lines: 722)
|
||||||
|
|
||||||
|
linux_alternates (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||||
|
-------------------------------------------------------------------------
|
||||||
|
rg* 0.087 +/- 0.000 (lines: 140)*
|
||||||
|
ag 0.330 +/- 0.002 (lines: 140)
|
||||||
|
git grep 0.414 +/- 0.047 (lines: 140)
|
||||||
|
ugrep 0.179 +/- 0.002 (lines: 140)
|
||||||
|
|
||||||
|
linux_alternates_casei (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
rg* 0.123 +/- 0.001 (lines: 241)*
|
||||||
|
ag 0.530 +/- 0.001 (lines: 241)
|
||||||
|
git grep 0.792 +/- 0.036 (lines: 241)
|
||||||
|
ugrep 0.177 +/- 0.003 (lines: 241)
|
||||||
|
|
||||||
|
subtitles_en_literal (pattern: Sherlock Holmes)
|
||||||
|
-----------------------------------------------
|
||||||
|
rg* 0.123 +/- 0.003 (lines: 830)*
|
||||||
|
rg (no mmap) 0.176 +/- 0.005 (lines: 830)
|
||||||
|
grep 0.572 +/- 0.017 (lines: 830)
|
||||||
|
rg (lines) 0.189 +/- 0.006 (lines: 830)
|
||||||
|
ag (lines) 1.868 +/- 0.004 (lines: 830)
|
||||||
|
grep (lines) 0.980 +/- 0.036 (lines: 830)
|
||||||
|
ugrep (lines) 0.185 +/- 0.007 (lines: 830)
|
||||||
|
|
||||||
|
subtitles_en_literal_casei (pattern: Sherlock Holmes)
|
||||||
|
-----------------------------------------------------
|
||||||
|
rg* 0.214 +/- 0.008 (lines: 871)*
|
||||||
|
grep 2.224 +/- 0.000 (lines: 871)
|
||||||
|
grep (ASCII) 0.671 +/- 0.001 (lines: 871)
|
||||||
|
rg (lines) 0.259 +/- 0.004 (lines: 871)
|
||||||
|
ag (lines) (ASCII) 1.897 +/- 0.026 (lines: 871)
|
||||||
|
ugrep (lines) 0.785 +/- 0.002 (lines: 871)
|
||||||
|
|
||||||
|
subtitles_en_literal_word (pattern: Sherlock Holmes)
|
||||||
|
----------------------------------------------------
|
||||||
|
rg (ASCII) 0.189 +/- 0.006 (lines: 830)
|
||||||
|
ag (ASCII) 1.842 +/- 0.023 (lines: 830)
|
||||||
|
grep (ASCII) 0.977 +/- 0.046 (lines: 830)
|
||||||
|
ugrep (ASCII)* 0.182 +/- 0.007 (lines: 830)*
|
||||||
|
rg 0.192 +/- 0.006 (lines: 830)
|
||||||
|
grep 0.990 +/- 0.024 (lines: 830)
|
||||||
|
|
||||||
|
subtitles_en_alternate (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||||
|
---------------------------------------------------------------------------------------------------------------
|
||||||
|
rg (lines) 0.248 +/- 0.001 (lines: 1094)
|
||||||
|
ag (lines) 2.638 +/- 0.055 (lines: 1094)
|
||||||
|
grep (lines) 2.052 +/- 0.027 (lines: 1094)
|
||||||
|
ugrep (lines) 0.787 +/- 0.001 (lines: 1094)
|
||||||
|
rg* 0.176 +/- 0.011 (lines: 1094)*
|
||||||
|
grep 1.660 +/- 0.002 (lines: 1094)
|
||||||
|
|
||||||
|
subtitles_en_alternate_casei (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||||
|
---------------------------------------------------------------------------------------------------------------------
|
||||||
|
ag (ASCII) 3.999 +/- 0.007 (lines: 1136)
|
||||||
|
grep (ASCII) 3.561 +/- 0.023 (lines: 1136)
|
||||||
|
ugrep (ASCII) 0.787 +/- 0.002 (lines: 1136)
|
||||||
|
rg* 0.483 +/- 0.009 (lines: 1136)*
|
||||||
|
grep 3.585 +/- 0.018 (lines: 1136)
|
||||||
|
|
||||||
|
subtitles_en_surrounding_words (pattern: \w+\s+Holmes\s+\w+)
|
||||||
|
------------------------------------------------------------
|
||||||
|
rg 0.200 +/- 0.001 (lines: 483)
|
||||||
|
grep 1.303 +/- 0.040 (lines: 483)
|
||||||
|
ugrep 43.220 +/- 0.047 (lines: 483)
|
||||||
|
rg (ASCII)* 0.197 +/- 0.000 (lines: 483)*
|
||||||
|
ag (ASCII) 5.223 +/- 0.056 (lines: 489)
|
||||||
|
grep (ASCII) 1.316 +/- 0.043 (lines: 483)
|
||||||
|
ugrep (ASCII) 17.647 +/- 0.219 (lines: 483)
|
||||||
|
|
||||||
|
subtitles_en_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||||
|
----------------------------------------------------------------------------------------
|
||||||
|
rg 2.119 +/- 0.016 (lines: 22)
|
||||||
|
ugrep 13.053 +/- 0.004 (lines: 22)
|
||||||
|
rg (ASCII)* 2.004 +/- 0.013 (lines: 22)*
|
||||||
|
ag (ASCII) 6.830 +/- 0.018 (lines: 302)
|
||||||
|
grep (ASCII) 4.395 +/- 0.030 (lines: 22)
|
||||||
|
ugrep (ASCII) 3.510 +/- 0.004 (lines: 22)
|
||||||
|
|
||||||
|
subtitles_ru_literal (pattern: Шерлок Холмс)
|
||||||
|
--------------------------------------------
|
||||||
|
rg* 0.133 +/- 0.002 (lines: 583)*
|
||||||
|
rg (no mmap) 0.180 +/- 0.000 (lines: 583)
|
||||||
|
grep 0.510 +/- 0.009 (lines: 583)
|
||||||
|
rg (lines) 0.193 +/- 0.003 (lines: 583)
|
||||||
|
ag (lines) 1.973 +/- 0.025 (lines: 583)
|
||||||
|
grep (lines) 0.808 +/- 0.006 (lines: 583)
|
||||||
|
ugrep (lines) 0.680 +/- 0.026 (lines: 583)
|
||||||
|
|
||||||
|
subtitles_ru_literal_casei (pattern: Шерлок Холмс)
|
||||||
|
--------------------------------------------------
|
||||||
|
rg* 0.268 +/- 0.001 (lines: 604)*
|
||||||
|
grep 4.767 +/- 0.002 (lines: 604)
|
||||||
|
grep (ASCII) 0.506 +/- 0.007 (lines: 583)
|
||||||
|
rg (lines) 0.335 +/- 0.005 (lines: 604)
|
||||||
|
ag (lines) (ASCII) 0.457 +/- 0.019 (lines: 0)
|
||||||
|
ugrep (lines) (ASCII) 0.720 +/- 0.081 (lines: 583)
|
||||||
|
|
||||||
|
subtitles_ru_literal_word (pattern: Шерлок Холмс)
|
||||||
|
-------------------------------------------------
|
||||||
|
rg (ASCII)* 0.195 +/- 0.011 (lines: 583)*
|
||||||
|
ag (ASCII) 0.509 +/- 0.020 (lines: 0)
|
||||||
|
grep (ASCII) 0.800 +/- 0.019 (lines: 583)
|
||||||
|
ugrep (ASCII) 0.708 +/- 0.034 (lines: 583)
|
||||||
|
rg 0.201 +/- 0.003 (lines: 579)
|
||||||
|
grep 0.792 +/- 0.020 (lines: 579)
|
||||||
|
|
||||||
|
subtitles_ru_alternate (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||||
|
-----------------------------------------------------------------------------------------------------------
|
||||||
|
rg (lines) 0.682 +/- 0.018 (lines: 691)
|
||||||
|
ag (lines) 2.722 +/- 0.020 (lines: 691)
|
||||||
|
grep (lines) 5.711 +/- 0.006 (lines: 691)
|
||||||
|
ugrep (lines) 8.301 +/- 0.023 (lines: 691)
|
||||||
|
rg* 0.625 +/- 0.012 (lines: 691)*
|
||||||
|
grep 5.425 +/- 0.006 (lines: 691)
|
||||||
|
|
||||||
|
subtitles_ru_alternate_casei (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||||
|
-----------------------------------------------------------------------------------------------------------------
|
||||||
|
ag (ASCII)* 2.727 +/- 0.019 (lines: 691)*
|
||||||
|
grep (ASCII) 5.712 +/- 0.002 (lines: 691)
|
||||||
|
ugrep (ASCII) 8.301 +/- 0.011 (lines: 691)
|
||||||
|
rg 3.673 +/- 0.004 (lines: 735)
|
||||||
|
grep 5.360 +/- 0.015 (lines: 735)
|
||||||
|
|
||||||
|
subtitles_ru_surrounding_words (pattern: \w+\s+Холмс\s+\w+)
|
||||||
|
-----------------------------------------------------------
|
||||||
|
rg* 0.203 +/- 0.001 (lines: 278)*
|
||||||
|
grep 1.039 +/- 0.009 (lines: 278)
|
||||||
|
ugrep 42.919 +/- 0.087 (lines: 278)
|
||||||
|
ag (ASCII) 1.084 +/- 0.001 (lines: 0)
|
||||||
|
grep (ASCII) 1.007 +/- 0.018 (lines: 0)
|
||||||
|
ugrep (ASCII) 0.890 +/- 0.001 (lines: 0)
|
||||||
|
|
||||||
|
subtitles_ru_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||||
|
----------------------------------------------------------------------------------------
|
||||||
|
rg 2.236 +/- 0.019 (lines: 41)
|
||||||
|
ugrep 28.811 +/- 0.127 (lines: 41)
|
||||||
|
rg (ASCII) 2.035 +/- 0.014 (lines: 0)
|
||||||
|
ag (ASCII) 1.093 +/- 0.006 (lines: 0)
|
||||||
|
grep (ASCII) 1.085 +/- 0.015 (lines: 0)
|
||||||
|
ugrep (ASCII)* 0.832 +/- 0.002 (lines: 0)*
|
271
build.rs
271
build.rs
@ -1,239 +1,46 @@
|
|||||||
use std::env;
|
|
||||||
use std::fs::{self, File};
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use clap::Shell;
|
|
||||||
|
|
||||||
use app::{RGArg, RGArgKind};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[path = "crates/core/app.rs"]
|
|
||||||
mod app;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// OUT_DIR is set by Cargo and it's where any additional build artifacts
|
set_git_revision_hash();
|
||||||
// are written.
|
set_windows_exe_options();
|
||||||
let outdir = match env::var_os("OUT_DIR") {
|
|
||||||
Some(outdir) => outdir,
|
|
||||||
None => {
|
|
||||||
eprintln!(
|
|
||||||
"OUT_DIR environment variable not defined. \
|
|
||||||
Please file a bug: \
|
|
||||||
https://github.com/BurntSushi/ripgrep/issues/new"
|
|
||||||
);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fs::create_dir_all(&outdir).unwrap();
|
|
||||||
|
|
||||||
let stamp_path = Path::new(&outdir).join("ripgrep-stamp");
|
|
||||||
if let Err(err) = File::create(&stamp_path) {
|
|
||||||
panic!("failed to write {}: {}", stamp_path.display(), err);
|
|
||||||
}
|
|
||||||
if let Err(err) = generate_man_page(&outdir) {
|
|
||||||
eprintln!("failed to generate man page: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use clap to build completion files.
|
|
||||||
let mut app = app::app();
|
|
||||||
app.gen_completions("rg", Shell::Bash, &outdir);
|
|
||||||
app.gen_completions("rg", Shell::Fish, &outdir);
|
|
||||||
app.gen_completions("rg", Shell::PowerShell, &outdir);
|
|
||||||
// Note that we do not use clap's support for zsh. Instead, zsh completions
|
|
||||||
// are manually maintained in `complete/_rg`.
|
|
||||||
|
|
||||||
// Make the current git hash available to the build.
|
|
||||||
if let Some(rev) = git_revision_hash() {
|
|
||||||
println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", rev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn git_revision_hash() -> Option<String> {
|
/// Embed a Windows manifest and set some linker options.
|
||||||
let result = process::Command::new("git")
|
///
|
||||||
.args(&["rev-parse", "--short=10", "HEAD"])
|
/// The main reason for this is to enable long path support on Windows. This
|
||||||
.output();
|
/// still, I believe, requires enabling long path support in the registry. But
|
||||||
result.ok().and_then(|output| {
|
/// if that's enabled, then this will let ripgrep use C:\... style paths that
|
||||||
let v = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
/// are longer than 260 characters.
|
||||||
if v.is_empty() {
|
fn set_windows_exe_options() {
|
||||||
None
|
static MANIFEST: &str = "pkg/windows/Manifest.xml";
|
||||||
} else {
|
|
||||||
Some(v)
|
let Ok(target_os) = std::env::var("CARGO_CFG_TARGET_OS") else { return };
|
||||||
}
|
let Ok(target_env) = std::env::var("CARGO_CFG_TARGET_ENV") else { return };
|
||||||
})
|
if !(target_os == "windows" && target_env == "msvc") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(mut manifest) = std::env::current_dir() else { return };
|
||||||
|
manifest.push(MANIFEST);
|
||||||
|
let Some(manifest) = manifest.to_str() else { return };
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed={}", MANIFEST);
|
||||||
|
// Embed the Windows application manifest file.
|
||||||
|
println!("cargo:rustc-link-arg-bin=rg=/MANIFEST:EMBED");
|
||||||
|
println!("cargo:rustc-link-arg-bin=rg=/MANIFESTINPUT:{manifest}");
|
||||||
|
// Turn linker warnings into errors. Helps debugging, otherwise the
|
||||||
|
// warnings get squashed (I believe).
|
||||||
|
println!("cargo:rustc-link-arg-bin=rg=/WX");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_man_page<P: AsRef<Path>>(outdir: P) -> io::Result<()> {
|
/// Make the current git hash available to the build as the environment
|
||||||
// If asciidoctor isn't installed, fallback to asciidoc.
|
/// variable `RIPGREP_BUILD_GIT_HASH`.
|
||||||
if let Err(err) = process::Command::new("asciidoctor").output() {
|
fn set_git_revision_hash() {
|
||||||
eprintln!(
|
use std::process::Command;
|
||||||
"Could not run 'asciidoctor' binary, falling back to 'a2x'."
|
|
||||||
);
|
let args = &["rev-parse", "--short=10", "HEAD"];
|
||||||
eprintln!("Error from running 'asciidoctor': {}", err);
|
let Ok(output) = Command::new("git").args(args).output() else { return };
|
||||||
return legacy_generate_man_page::<P>(outdir);
|
let rev = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
|
if rev.is_empty() {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// 1. Read asciidoctor template.
|
println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", rev);
|
||||||
// 2. Interpolate template with auto-generated docs.
|
|
||||||
// 3. Save interpolation to disk.
|
|
||||||
// 4. Use asciidoctor to convert to man page.
|
|
||||||
let outdir = outdir.as_ref();
|
|
||||||
let cwd = env::current_dir()?;
|
|
||||||
let tpl_path = cwd.join("doc").join("rg.1.txt.tpl");
|
|
||||||
let txt_path = outdir.join("rg.1.txt");
|
|
||||||
|
|
||||||
let mut tpl = String::new();
|
|
||||||
File::open(&tpl_path)?.read_to_string(&mut tpl)?;
|
|
||||||
let options =
|
|
||||||
formatted_options()?.replace("{", "{").replace("}", "}");
|
|
||||||
tpl = tpl.replace("{OPTIONS}", &options);
|
|
||||||
|
|
||||||
let githash = git_revision_hash();
|
|
||||||
let githash = githash.as_ref().map(|x| &**x);
|
|
||||||
tpl = tpl.replace("{VERSION}", &app::long_version(githash, false));
|
|
||||||
|
|
||||||
File::create(&txt_path)?.write_all(tpl.as_bytes())?;
|
|
||||||
let result = process::Command::new("asciidoctor")
|
|
||||||
.arg("--doctype")
|
|
||||||
.arg("manpage")
|
|
||||||
.arg("--backend")
|
|
||||||
.arg("manpage")
|
|
||||||
.arg(&txt_path)
|
|
||||||
.spawn()?
|
|
||||||
.wait()?;
|
|
||||||
if !result.success() {
|
|
||||||
let msg =
|
|
||||||
format!("'asciidoctor' failed with exit code {:?}", result.code());
|
|
||||||
return Err(ioerr(msg));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn legacy_generate_man_page<P: AsRef<Path>>(outdir: P) -> io::Result<()> {
|
|
||||||
// If asciidoc isn't installed, then don't do anything.
|
|
||||||
if let Err(err) = process::Command::new("a2x").output() {
|
|
||||||
eprintln!("Could not run 'a2x' binary, skipping man page generation.");
|
|
||||||
eprintln!("Error from running 'a2x': {}", err);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// 1. Read asciidoc template.
|
|
||||||
// 2. Interpolate template with auto-generated docs.
|
|
||||||
// 3. Save interpolation to disk.
|
|
||||||
// 4. Use a2x (part of asciidoc) to convert to man page.
|
|
||||||
let outdir = outdir.as_ref();
|
|
||||||
let cwd = env::current_dir()?;
|
|
||||||
let tpl_path = cwd.join("doc").join("rg.1.txt.tpl");
|
|
||||||
let txt_path = outdir.join("rg.1.txt");
|
|
||||||
|
|
||||||
let mut tpl = String::new();
|
|
||||||
File::open(&tpl_path)?.read_to_string(&mut tpl)?;
|
|
||||||
tpl = tpl.replace("{OPTIONS}", &formatted_options()?);
|
|
||||||
|
|
||||||
let githash = git_revision_hash();
|
|
||||||
let githash = githash.as_ref().map(|x| &**x);
|
|
||||||
tpl = tpl.replace("{VERSION}", &app::long_version(githash, false));
|
|
||||||
|
|
||||||
File::create(&txt_path)?.write_all(tpl.as_bytes())?;
|
|
||||||
let result = process::Command::new("a2x")
|
|
||||||
.arg("--no-xmllint")
|
|
||||||
.arg("--doctype")
|
|
||||||
.arg("manpage")
|
|
||||||
.arg("--format")
|
|
||||||
.arg("manpage")
|
|
||||||
.arg(&txt_path)
|
|
||||||
.spawn()?
|
|
||||||
.wait()?;
|
|
||||||
if !result.success() {
|
|
||||||
let msg = format!("'a2x' failed with exit code {:?}", result.code());
|
|
||||||
return Err(ioerr(msg));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn formatted_options() -> io::Result<String> {
|
|
||||||
let mut args = app::all_args_and_flags();
|
|
||||||
args.sort_by(|x1, x2| x1.name.cmp(&x2.name));
|
|
||||||
|
|
||||||
let mut formatted = vec![];
|
|
||||||
for arg in args {
|
|
||||||
if arg.hidden {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// ripgrep only has two positional arguments, and probably will only
|
|
||||||
// ever have two positional arguments, so we just hardcode them into
|
|
||||||
// the template.
|
|
||||||
if let app::RGArgKind::Positional { .. } = arg.kind {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
formatted.push(formatted_arg(&arg)?);
|
|
||||||
}
|
|
||||||
Ok(formatted.join("\n\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn formatted_arg(arg: &RGArg) -> io::Result<String> {
|
|
||||||
match arg.kind {
|
|
||||||
RGArgKind::Positional { .. } => {
|
|
||||||
panic!("unexpected positional argument")
|
|
||||||
}
|
|
||||||
RGArgKind::Switch { long, short, multiple } => {
|
|
||||||
let mut out = vec![];
|
|
||||||
|
|
||||||
let mut header = format!("--{}", long);
|
|
||||||
if let Some(short) = short {
|
|
||||||
header = format!("-{}, {}", short, header);
|
|
||||||
}
|
|
||||||
if multiple {
|
|
||||||
header = format!("*{}* ...::", header);
|
|
||||||
} else {
|
|
||||||
header = format!("*{}*::", header);
|
|
||||||
}
|
|
||||||
writeln!(out, "{}", header)?;
|
|
||||||
writeln!(out, "{}", formatted_doc_txt(arg)?)?;
|
|
||||||
|
|
||||||
Ok(String::from_utf8(out).unwrap())
|
|
||||||
}
|
|
||||||
RGArgKind::Flag { long, short, value_name, multiple, .. } => {
|
|
||||||
let mut out = vec![];
|
|
||||||
|
|
||||||
let mut header = format!("--{}", long);
|
|
||||||
if let Some(short) = short {
|
|
||||||
header = format!("-{}, {}", short, header);
|
|
||||||
}
|
|
||||||
if multiple {
|
|
||||||
header = format!("*{}* _{}_ ...::", header, value_name);
|
|
||||||
} else {
|
|
||||||
header = format!("*{}* _{}_::", header, value_name);
|
|
||||||
}
|
|
||||||
writeln!(out, "{}", header)?;
|
|
||||||
writeln!(out, "{}", formatted_doc_txt(arg)?)?;
|
|
||||||
|
|
||||||
Ok(String::from_utf8(out).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn formatted_doc_txt(arg: &RGArg) -> io::Result<String> {
|
|
||||||
let paragraphs: Vec<String> = arg
|
|
||||||
.doc_long
|
|
||||||
.replace("{", "{")
|
|
||||||
.replace("}", r"}")
|
|
||||||
// Hack to render ** literally in man page correctly. We can't put
|
|
||||||
// these crazy +++ in the help text directly, since that shows
|
|
||||||
// literally in --help output.
|
|
||||||
.replace("*-g 'foo/**'*", "*-g +++'foo/**'+++*")
|
|
||||||
.split("\n\n")
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect();
|
|
||||||
if paragraphs.is_empty() {
|
|
||||||
return Err(ioerr(format!("missing docs for --{}", arg.name)));
|
|
||||||
}
|
|
||||||
let first = format!(" {}", paragraphs[0].replace("\n", "\n "));
|
|
||||||
if paragraphs.len() == 1 {
|
|
||||||
return Ok(first);
|
|
||||||
}
|
|
||||||
Ok(format!("{}\n+\n{}", first, paragraphs[1..].join("\n+\n")))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ioerr(msg: String) -> io::Error {
|
|
||||||
io::Error::new(io::ErrorKind::Other, msg)
|
|
||||||
}
|
}
|
||||||
|
43
ci/build-and-publish-m2
Executable file
43
ci/build-and-publish-m2
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script builds a ripgrep release for the aarch64-apple-darwin target.
|
||||||
|
# At time of writing (2023-11-21), GitHub Actions does not free Apple silicon
|
||||||
|
# runners. Since I have somewhat recently acquired an M2 mac mini, I just use
|
||||||
|
# this script to build the release tarball and upload it with `gh`.
|
||||||
|
#
|
||||||
|
# Once GitHub Actions has proper support for Apple silicon, we should add it
|
||||||
|
# to our release workflow and drop this script.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
version="$1"
|
||||||
|
if [ -z "$version" ]; then
|
||||||
|
echo "missing version" >&2
|
||||||
|
echo "Usage: "$(basename "$0")" <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q "version = \"$version\"" Cargo.toml; then
|
||||||
|
echo "version does not match Cargo.toml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
target=aarch64-apple-darwin
|
||||||
|
cargo build --release --features pcre2 --target $target
|
||||||
|
BIN=target/$target/release/rg
|
||||||
|
NAME=ripgrep-$version-$target
|
||||||
|
ARCHIVE="deployment/m2/$NAME"
|
||||||
|
|
||||||
|
mkdir -p "$ARCHIVE"/{complete,doc}
|
||||||
|
cp "$BIN" "$ARCHIVE"/
|
||||||
|
strip "$ARCHIVE/rg"
|
||||||
|
cp {README.md,COPYING,UNLICENSE,LICENSE-MIT} "$ARCHIVE"/
|
||||||
|
cp {CHANGELOG.md,FAQ.md,GUIDE.md} "$ARCHIVE"/doc/
|
||||||
|
"$BIN" --generate complete-bash > "$ARCHIVE/complete/rg.bash"
|
||||||
|
"$BIN" --generate complete-fish > "$ARCHIVE/complete/rg.fish"
|
||||||
|
"$BIN" --generate complete-powershell > "$ARCHIVE/complete/_rg.ps1"
|
||||||
|
"$BIN" --generate complete-zsh > "$ARCHIVE/complete/_rg"
|
||||||
|
"$BIN" --generate man > "$ARCHIVE/doc/rg.1"
|
||||||
|
|
||||||
|
tar c -C deployment/m2 -z -f "$ARCHIVE.tar.gz" "$NAME"
|
||||||
|
shasum -a 256 "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256"
|
||||||
|
gh release upload "$version" "$ARCHIVE.tar.gz" "$ARCHIVE.tar.gz.sha256"
|
42
ci/build-deb
42
ci/build-deb
@ -1,42 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
D="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
|
||||||
|
|
||||||
# This script builds a binary dpkg for Debian based distros. It does not
|
|
||||||
# currently run in CI, and is instead run manually and the resulting dpkg is
|
|
||||||
# uploaded to GitHub via the web UI.
|
|
||||||
#
|
|
||||||
# Note that this requires 'cargo deb', which can be installed with
|
|
||||||
# 'cargo install cargo-deb'.
|
|
||||||
#
|
|
||||||
# This should be run from the root of the ripgrep repo.
|
|
||||||
|
|
||||||
if ! command -V cargo-deb > /dev/null 2>&1; then
|
|
||||||
echo "cargo-deb command missing" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -V asciidoctor > /dev/null 2>&1; then
|
|
||||||
echo "asciidoctor command missing" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 'cargo deb' does not seem to provide a way to specify an asset that is
|
|
||||||
# created at build time, such as ripgrep's man page. To work around this,
|
|
||||||
# we force a debug build, copy out the man page (and shell completions)
|
|
||||||
# produced from that build, put it into a predictable location and then build
|
|
||||||
# the deb, which knows where to look.
|
|
||||||
cargo build
|
|
||||||
|
|
||||||
DEPLOY_DIR=deployment/deb
|
|
||||||
OUT_DIR="$("$D"/cargo-out-dir target/debug/)"
|
|
||||||
mkdir -p "$DEPLOY_DIR"
|
|
||||||
|
|
||||||
# Copy man page and shell completions.
|
|
||||||
cp "$OUT_DIR"/{rg.1,rg.bash,rg.fish} "$DEPLOY_DIR/"
|
|
||||||
cp complete/_rg "$DEPLOY_DIR/"
|
|
||||||
|
|
||||||
# Since we're distributing the dpkg, we don't know whether the user will have
|
|
||||||
# PCRE2 installed, so just do a static build.
|
|
||||||
PCRE2_SYS_STATIC=1 cargo deb
|
|
@ -1,19 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Finds Cargo's `OUT_DIR` directory from the most recent build.
|
|
||||||
#
|
|
||||||
# This requires one parameter corresponding to the target directory
|
|
||||||
# to search for the build output.
|
|
||||||
|
|
||||||
if [ $# != 1 ]; then
|
|
||||||
echo "Usage: $(basename "$0") <target-dir>" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This works by finding the most recent stamp file, which is produced by
|
|
||||||
# every ripgrep build.
|
|
||||||
target_dir="$1"
|
|
||||||
find "$target_dir" -name ripgrep-stamp -print0 \
|
|
||||||
| xargs -0 ls -t \
|
|
||||||
| head -n1 \
|
|
||||||
| xargs dirname
|
|
@ -1,24 +0,0 @@
|
|||||||
These are Docker images used for cross compilation in CI builds (or locally)
|
|
||||||
via the [Cross](https://github.com/rust-embedded/cross) tool.
|
|
||||||
|
|
||||||
The Cross tool actually provides its own Docker images, and all Docker images
|
|
||||||
in this directory are derived from one of them. We provide our own in order
|
|
||||||
to customize the environment. For example, we need to install some things like
|
|
||||||
`asciidoctor` in order to generate man pages. We also install compression tools
|
|
||||||
like `xz` so that tests for the `-z/--search-zip` flag are run.
|
|
||||||
|
|
||||||
If you make a change to a Docker image, then you can re-build it. `cd` into the
|
|
||||||
directory containing the `Dockerfile` and run:
|
|
||||||
|
|
||||||
$ cd x86_64-unknown-linux-musl
|
|
||||||
$ ./build
|
|
||||||
|
|
||||||
At this point, subsequent uses of `cross` will now use your built image since
|
|
||||||
Docker prefers local images over remote images. In order to make these changes
|
|
||||||
stick, they need to be pushed to Docker Hub:
|
|
||||||
|
|
||||||
$ docker push burntsushi/cross:x86_64-unknown-linux-musl
|
|
||||||
|
|
||||||
Of course, only I (BurntSushi) can push to that location. To make `cross` use
|
|
||||||
a different location, then edit `Cross.toml` in the root of this repo to use
|
|
||||||
a different image name for the desired target.
|
|
@ -1,4 +0,0 @@
|
|||||||
FROM rustembedded/cross:arm-unknown-linux-gnueabihf
|
|
||||||
|
|
||||||
COPY stage/ubuntu-install-packages /
|
|
||||||
RUN /ubuntu-install-packages
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
mkdir -p stage
|
|
||||||
cp ../../ubuntu-install-packages ./stage/
|
|
||||||
docker build -t burntsushi/cross:arm-unknown-linux-gnueabihf .
|
|
@ -1,4 +0,0 @@
|
|||||||
FROM rustembedded/cross:i686-unknown-linux-gnu
|
|
||||||
|
|
||||||
COPY stage/ubuntu-install-packages /
|
|
||||||
RUN /ubuntu-install-packages
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
mkdir -p stage
|
|
||||||
cp ../../ubuntu-install-packages ./stage/
|
|
||||||
docker build -t burntsushi/cross:i686-unknown-linux-gnu .
|
|
@ -1,4 +0,0 @@
|
|||||||
FROM rustembedded/cross:mips64-unknown-linux-gnuabi64
|
|
||||||
|
|
||||||
COPY stage/ubuntu-install-packages /
|
|
||||||
RUN /ubuntu-install-packages
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
mkdir -p stage
|
|
||||||
cp ../../ubuntu-install-packages ./stage/
|
|
||||||
docker build -t burntsushi/cross:mips64-unknown-linux-gnuabi64 .
|
|
@ -1,4 +0,0 @@
|
|||||||
FROM rustembedded/cross:x86_64-unknown-linux-musl
|
|
||||||
|
|
||||||
COPY stage/ubuntu-install-packages /
|
|
||||||
RUN /ubuntu-install-packages
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
mkdir -p stage
|
|
||||||
cp ../../ubuntu-install-packages ./stage/
|
|
||||||
docker build -t burntsushi/cross:x86_64-unknown-linux-musl .
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
brew install asciidoctor
|
|
@ -19,7 +19,7 @@ get_comp_args() {
|
|||||||
main() {
|
main() {
|
||||||
local diff
|
local diff
|
||||||
local rg="${0:a:h}/../${TARGET_DIR:-target}/release/rg"
|
local rg="${0:a:h}/../${TARGET_DIR:-target}/release/rg"
|
||||||
local _rg="${0:a:h}/../complete/_rg"
|
local _rg="${0:a:h}/../crates/core/flags/complete/rg.zsh"
|
||||||
local -a help_args comp_args
|
local -a help_args comp_args
|
||||||
|
|
||||||
[[ -e $rg ]] || rg=${rg/%\/release\/rg/\/debug\/rg}
|
[[ -e $rg ]] || rg=${rg/%\/release\/rg/\/debug\/rg}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script gets run in weird environments that have been stripped of just
|
||||||
|
# about every inessential thing. In order to keep this script versatile, we
|
||||||
|
# just install 'sudo' and use it like normal if it doesn't exist. If it doesn't
|
||||||
|
# exist, we assume we're root. (Otherwise we ain't doing much of anything
|
||||||
|
# anyway.)
|
||||||
|
if ! command -V sudo; then
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends sudo
|
||||||
|
fi
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y --no-install-recommends \
|
sudo apt-get install -y --no-install-recommends \
|
||||||
asciidoctor \
|
zsh xz-utils liblz4-tool musl-tools brotli zstd
|
||||||
zsh xz-utils liblz4-tool musl-tools
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-cli"
|
name = "grep-cli"
|
||||||
version = "0.1.6" #:version
|
version = "0.1.11" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Utilities for search oriented command line applications.
|
Utilities for search oriented command line applications.
|
||||||
@ -10,18 +10,17 @@ homepage = "https://github.com/BurntSushi/ripgrep/tree/master/crates/cli"
|
|||||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/cli"
|
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/cli"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["regex", "grep", "cli", "utility", "util"]
|
keywords = ["regex", "grep", "cli", "utility", "util"]
|
||||||
license = "Unlicense/MIT"
|
license = "Unlicense OR MIT"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atty = "0.2.11"
|
bstr = { version = "1.6.2", features = ["std"] }
|
||||||
bstr = "0.2.0"
|
globset = { version = "0.4.15", path = "../globset" }
|
||||||
globset = { version = "0.4.7", path = "../globset" }
|
log = "0.4.20"
|
||||||
lazy_static = "1.1.0"
|
termcolor = "1.3.0"
|
||||||
log = "0.4.5"
|
|
||||||
regex = "1.1"
|
|
||||||
same-file = "1.0.4"
|
|
||||||
termcolor = "1.0.4"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.winapi-util]
|
[target.'cfg(windows)'.dependencies.winapi-util]
|
||||||
version = "0.1.1"
|
version = "0.1.6"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies.libc]
|
||||||
|
version = "0.2.148"
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use std::ffi::{OsStr, OsString};
|
use std::{
|
||||||
use std::fs::File;
|
ffi::{OsStr, OsString},
|
||||||
use std::io;
|
fs::File,
|
||||||
use std::path::{Path, PathBuf};
|
io,
|
||||||
use std::process::Command;
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
@ -18,7 +20,7 @@ pub struct DecompressionMatcherBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A representation of a single command for decompressing data
|
/// A representation of a single command for decompressing data
|
||||||
/// out-of-proccess.
|
/// out-of-process.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct DecompressionCommand {
|
struct DecompressionCommand {
|
||||||
/// The glob that matches this command.
|
/// The glob that matches this command.
|
||||||
@ -132,7 +134,7 @@ impl DecompressionMatcherBuilder {
|
|||||||
A: AsRef<OsStr>,
|
A: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
let glob = glob.to_string();
|
let glob = glob.to_string();
|
||||||
let bin = resolve_binary(Path::new(program.as_ref()))?;
|
let bin = try_resolve_binary(Path::new(program.as_ref()))?;
|
||||||
let args =
|
let args =
|
||||||
args.into_iter().map(|a| a.as_ref().to_os_string()).collect();
|
args.into_iter().map(|a| a.as_ref().to_os_string()).collect();
|
||||||
self.commands.push(DecompressionCommand { glob, bin, args });
|
self.commands.push(DecompressionCommand { glob, bin, args });
|
||||||
@ -161,7 +163,7 @@ impl DecompressionMatcher {
|
|||||||
/// Create a new matcher with default rules.
|
/// Create a new matcher with default rules.
|
||||||
///
|
///
|
||||||
/// To add more matching rules, build a matcher with
|
/// To add more matching rules, build a matcher with
|
||||||
/// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html).
|
/// [`DecompressionMatcherBuilder`].
|
||||||
pub fn new() -> DecompressionMatcher {
|
pub fn new() -> DecompressionMatcher {
|
||||||
DecompressionMatcherBuilder::new()
|
DecompressionMatcherBuilder::new()
|
||||||
.build()
|
.build()
|
||||||
@ -221,9 +223,8 @@ impl DecompressionReaderBuilder {
|
|||||||
path: P,
|
path: P,
|
||||||
) -> Result<DecompressionReader, CommandError> {
|
) -> Result<DecompressionReader, CommandError> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let mut cmd = match self.matcher.command(path) {
|
let Some(mut cmd) = self.matcher.command(path) else {
|
||||||
None => return DecompressionReader::new_passthru(path),
|
return DecompressionReader::new_passthru(path);
|
||||||
Some(cmd) => cmd,
|
|
||||||
};
|
};
|
||||||
cmd.arg(path);
|
cmd.arg(path);
|
||||||
|
|
||||||
@ -302,9 +303,7 @@ impl DecompressionReaderBuilder {
|
|||||||
/// The default matching rules are probably good enough for most cases, and if
|
/// The default matching rules are probably good enough for most cases, and if
|
||||||
/// they require revision, pull requests are welcome. In cases where they must
|
/// they require revision, pull requests are welcome. In cases where they must
|
||||||
/// be changed or extended, they can be customized through the use of
|
/// be changed or extended, they can be customized through the use of
|
||||||
/// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html)
|
/// [`DecompressionMatcherBuilder`] and [`DecompressionReaderBuilder`].
|
||||||
/// and
|
|
||||||
/// [`DecompressionReaderBuilder`](struct.DecompressionReaderBuilder.html).
|
|
||||||
///
|
///
|
||||||
/// By default, this reader will asynchronously read the processes' stderr.
|
/// By default, this reader will asynchronously read the processes' stderr.
|
||||||
/// This prevents subtle deadlocking bugs for noisy processes that write a lot
|
/// This prevents subtle deadlocking bugs for noisy processes that write a lot
|
||||||
@ -320,15 +319,14 @@ impl DecompressionReaderBuilder {
|
|||||||
/// matcher.
|
/// matcher.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use std::io::Read;
|
/// use std::{io::Read, process::Command};
|
||||||
/// use std::process::Command;
|
///
|
||||||
/// use grep_cli::DecompressionReader;
|
/// use grep_cli::DecompressionReader;
|
||||||
///
|
///
|
||||||
/// # fn example() -> Result<(), Box<::std::error::Error>> {
|
|
||||||
/// let mut rdr = DecompressionReader::new("/usr/share/man/man1/ls.1.gz")?;
|
/// let mut rdr = DecompressionReader::new("/usr/share/man/man1/ls.1.gz")?;
|
||||||
/// let mut contents = vec![];
|
/// let mut contents = vec![];
|
||||||
/// rdr.read_to_end(&mut contents)?;
|
/// rdr.read_to_end(&mut contents)?;
|
||||||
/// # Ok(()) }
|
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DecompressionReader {
|
pub struct DecompressionReader {
|
||||||
@ -347,9 +345,7 @@ impl DecompressionReader {
|
|||||||
///
|
///
|
||||||
/// This uses the default matching rules for determining how to decompress
|
/// This uses the default matching rules for determining how to decompress
|
||||||
/// the given file. To change those matching rules, use
|
/// the given file. To change those matching rules, use
|
||||||
/// [`DecompressionReaderBuilder`](struct.DecompressionReaderBuilder.html)
|
/// [`DecompressionReaderBuilder`] and [`DecompressionMatcherBuilder`].
|
||||||
/// and
|
|
||||||
/// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html).
|
|
||||||
///
|
///
|
||||||
/// When creating readers for many paths. it is better to use the builder
|
/// When creating readers for many paths. it is better to use the builder
|
||||||
/// since it will amortize the cost of constructing the matcher.
|
/// since it will amortize the cost of constructing the matcher.
|
||||||
@ -382,7 +378,7 @@ impl DecompressionReader {
|
|||||||
///
|
///
|
||||||
/// `close` is also called in `drop` as a last line of defense against
|
/// `close` is also called in `drop` as a last line of defense against
|
||||||
/// resource leakage. Any error from the child process is then printed as a
|
/// resource leakage. Any error from the child process is then printed as a
|
||||||
/// warning to stderr. This can be avoided by explictly calling `close`
|
/// warning to stderr. This can be avoided by explicitly calling `close`
|
||||||
/// before the CommandReader is dropped.
|
/// before the CommandReader is dropped.
|
||||||
pub fn close(&mut self) -> io::Result<()> {
|
pub fn close(&mut self) -> io::Result<()> {
|
||||||
match self.rdr {
|
match self.rdr {
|
||||||
@ -421,30 +417,52 @@ impl io::Read for DecompressionReader {
|
|||||||
/// On non-Windows, this is a no-op.
|
/// On non-Windows, this is a no-op.
|
||||||
pub fn resolve_binary<P: AsRef<Path>>(
|
pub fn resolve_binary<P: AsRef<Path>>(
|
||||||
prog: P,
|
prog: P,
|
||||||
|
) -> Result<PathBuf, CommandError> {
|
||||||
|
if !cfg!(windows) {
|
||||||
|
return Ok(prog.as_ref().to_path_buf());
|
||||||
|
}
|
||||||
|
try_resolve_binary(prog)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a path to a program to a path by searching for the program in
|
||||||
|
/// `PATH`.
|
||||||
|
///
|
||||||
|
/// If the program could not be resolved, then an error is returned.
|
||||||
|
///
|
||||||
|
/// The purpose of doing this instead of passing the path to the program
|
||||||
|
/// directly to Command::new is that Command::new will hand relative paths
|
||||||
|
/// to CreateProcess on Windows, which will implicitly search the current
|
||||||
|
/// working directory for the executable. This could be undesirable for
|
||||||
|
/// security reasons. e.g., running ripgrep with the -z/--search-zip flag on an
|
||||||
|
/// untrusted directory tree could result in arbitrary programs executing on
|
||||||
|
/// Windows.
|
||||||
|
///
|
||||||
|
/// Note that this could still return a relative path if PATH contains a
|
||||||
|
/// relative path. We permit this since it is assumed that the user has set
|
||||||
|
/// this explicitly, and thus, desires this behavior.
|
||||||
|
///
|
||||||
|
/// If `check_exists` is false or the path is already an absolute path this
|
||||||
|
/// will return immediately.
|
||||||
|
fn try_resolve_binary<P: AsRef<Path>>(
|
||||||
|
prog: P,
|
||||||
) -> Result<PathBuf, CommandError> {
|
) -> Result<PathBuf, CommandError> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
fn is_exe(path: &Path) -> bool {
|
fn is_exe(path: &Path) -> bool {
|
||||||
let md = match path.metadata() {
|
let Ok(md) = path.metadata() else { return false };
|
||||||
Err(_) => return false,
|
|
||||||
Ok(md) => md,
|
|
||||||
};
|
|
||||||
!md.is_dir()
|
!md.is_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
let prog = prog.as_ref();
|
let prog = prog.as_ref();
|
||||||
if !cfg!(windows) || prog.is_absolute() {
|
if prog.is_absolute() {
|
||||||
return Ok(prog.to_path_buf());
|
return Ok(prog.to_path_buf());
|
||||||
}
|
}
|
||||||
let syspaths = match env::var_os("PATH") {
|
let Some(syspaths) = env::var_os("PATH") else {
|
||||||
Some(syspaths) => syspaths,
|
let msg = "system PATH environment variable not found";
|
||||||
None => {
|
return Err(CommandError::io(io::Error::new(
|
||||||
let msg = "system PATH environment variable not found";
|
io::ErrorKind::Other,
|
||||||
return Err(CommandError::io(io::Error::new(
|
msg,
|
||||||
io::ErrorKind::Other,
|
)));
|
||||||
msg,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
for syspath in env::split_paths(&syspaths) {
|
for syspath in env::split_paths(&syspaths) {
|
||||||
if syspath.as_os_str().is_empty() {
|
if syspath.as_os_str().is_empty() {
|
||||||
@ -455,9 +473,11 @@ pub fn resolve_binary<P: AsRef<Path>>(
|
|||||||
return Ok(abs_prog.to_path_buf());
|
return Ok(abs_prog.to_path_buf());
|
||||||
}
|
}
|
||||||
if abs_prog.extension().is_none() {
|
if abs_prog.extension().is_none() {
|
||||||
let abs_prog = abs_prog.with_extension("exe");
|
for extension in ["com", "exe"] {
|
||||||
if is_exe(&abs_prog) {
|
let abs_prog = abs_prog.with_extension(extension);
|
||||||
return Ok(abs_prog.to_path_buf());
|
if is_exe(&abs_prog) {
|
||||||
|
return Ok(abs_prog.to_path_buf());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,14 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use bstr::{ByteSlice, ByteVec};
|
use bstr::{ByteSlice, ByteVec};
|
||||||
|
|
||||||
/// A single state in the state machine used by `unescape`.
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
||||||
enum State {
|
|
||||||
/// The state after seeing a `\`.
|
|
||||||
Escape,
|
|
||||||
/// The state after seeing a `\x`.
|
|
||||||
HexFirst,
|
|
||||||
/// The state after seeing a `\x[0-9A-Fa-f]`.
|
|
||||||
HexSecond(char),
|
|
||||||
/// Default state.
|
|
||||||
Literal,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Escapes arbitrary bytes into a human readable string.
|
/// Escapes arbitrary bytes into a human readable string.
|
||||||
///
|
///
|
||||||
/// This converts `\t`, `\r` and `\n` into their escaped forms. It also
|
/// This converts `\t`, `\r` and `\n` into their escaped forms. It also
|
||||||
/// converts the non-printable subset of ASCII in addition to invalid UTF-8
|
/// converts the non-printable subset of ASCII in addition to invalid UTF-8
|
||||||
/// bytes to hexadecimal escape sequences. Everything else is left as is.
|
/// bytes to hexadecimal escape sequences. Everything else is left as is.
|
||||||
///
|
///
|
||||||
/// The dual of this routine is [`unescape`](fn.unescape.html).
|
/// The dual of this routine is [`unescape`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -38,22 +24,12 @@ enum State {
|
|||||||
/// assert_eq!(r"foo\nbar\xFFbaz", escape(b"foo\nbar\xFFbaz"));
|
/// assert_eq!(r"foo\nbar\xFFbaz", escape(b"foo\nbar\xFFbaz"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn escape(bytes: &[u8]) -> String {
|
pub fn escape(bytes: &[u8]) -> String {
|
||||||
let mut escaped = String::new();
|
bytes.escape_bytes().to_string()
|
||||||
for (s, e, ch) in bytes.char_indices() {
|
|
||||||
if ch == '\u{FFFD}' {
|
|
||||||
for b in bytes[s..e].bytes() {
|
|
||||||
escape_byte(b, &mut escaped);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
escape_char(ch, &mut escaped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
escaped
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Escapes an OS string into a human readable string.
|
/// Escapes an OS string into a human readable string.
|
||||||
///
|
///
|
||||||
/// This is like [`escape`](fn.escape.html), but accepts an OS string.
|
/// This is like [`escape`], but accepts an OS string.
|
||||||
pub fn escape_os(string: &OsStr) -> String {
|
pub fn escape_os(string: &OsStr) -> String {
|
||||||
escape(Vec::from_os_str_lossy(string).as_bytes())
|
escape(Vec::from_os_str_lossy(string).as_bytes())
|
||||||
}
|
}
|
||||||
@ -72,7 +48,7 @@ pub fn escape_os(string: &OsStr) -> String {
|
|||||||
/// capable of specifying arbitrary bytes or otherwise make it easier to
|
/// capable of specifying arbitrary bytes or otherwise make it easier to
|
||||||
/// specify non-printable characters.
|
/// specify non-printable characters.
|
||||||
///
|
///
|
||||||
/// The dual of this routine is [`escape`](fn.escape.html).
|
/// The dual of this routine is [`escape`].
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -89,81 +65,12 @@ pub fn escape_os(string: &OsStr) -> String {
|
|||||||
/// assert_eq!(&b"foo\nbar\xFFbaz"[..], &*unescape(r"foo\nbar\xFFbaz"));
|
/// assert_eq!(&b"foo\nbar\xFFbaz"[..], &*unescape(r"foo\nbar\xFFbaz"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn unescape(s: &str) -> Vec<u8> {
|
pub fn unescape(s: &str) -> Vec<u8> {
|
||||||
use self::State::*;
|
Vec::unescape_bytes(s)
|
||||||
|
|
||||||
let mut bytes = vec![];
|
|
||||||
let mut state = Literal;
|
|
||||||
for c in s.chars() {
|
|
||||||
match state {
|
|
||||||
Escape => match c {
|
|
||||||
'\\' => {
|
|
||||||
bytes.push(b'\\');
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
'n' => {
|
|
||||||
bytes.push(b'\n');
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
'r' => {
|
|
||||||
bytes.push(b'\r');
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
't' => {
|
|
||||||
bytes.push(b'\t');
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
'x' => {
|
|
||||||
state = HexFirst;
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
bytes.extend(format!(r"\{}", c).into_bytes());
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
HexFirst => match c {
|
|
||||||
'0'..='9' | 'A'..='F' | 'a'..='f' => {
|
|
||||||
state = HexSecond(c);
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
bytes.extend(format!(r"\x{}", c).into_bytes());
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
HexSecond(first) => match c {
|
|
||||||
'0'..='9' | 'A'..='F' | 'a'..='f' => {
|
|
||||||
let ordinal = format!("{}{}", first, c);
|
|
||||||
let byte = u8::from_str_radix(&ordinal, 16).unwrap();
|
|
||||||
bytes.push(byte);
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
let original = format!(r"\x{}{}", first, c);
|
|
||||||
bytes.extend(original.into_bytes());
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Literal => match c {
|
|
||||||
'\\' => {
|
|
||||||
state = Escape;
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
bytes.extend(c.to_string().as_bytes());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match state {
|
|
||||||
Escape => bytes.push(b'\\'),
|
|
||||||
HexFirst => bytes.extend(b"\\x"),
|
|
||||||
HexSecond(c) => bytes.extend(format!("\\x{}", c).into_bytes()),
|
|
||||||
Literal => {}
|
|
||||||
}
|
|
||||||
bytes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unescapes an OS string.
|
/// Unescapes an OS string.
|
||||||
///
|
///
|
||||||
/// This is like [`unescape`](fn.unescape.html), but accepts an OS string.
|
/// This is like [`unescape`], but accepts an OS string.
|
||||||
///
|
///
|
||||||
/// Note that this first lossily decodes the given OS string as UTF-8. That
|
/// Note that this first lossily decodes the given OS string as UTF-8. That
|
||||||
/// is, an escaped string (the thing given) should be valid UTF-8.
|
/// is, an escaped string (the thing given) should be valid UTF-8.
|
||||||
@ -171,27 +78,6 @@ pub fn unescape_os(string: &OsStr) -> Vec<u8> {
|
|||||||
unescape(&string.to_string_lossy())
|
unescape(&string.to_string_lossy())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds the given codepoint to the given string, escaping it if necessary.
|
|
||||||
fn escape_char(cp: char, into: &mut String) {
|
|
||||||
if cp.is_ascii() {
|
|
||||||
escape_byte(cp as u8, into);
|
|
||||||
} else {
|
|
||||||
into.push(cp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds the given byte to the given string, escaping it if necessary.
|
|
||||||
fn escape_byte(byte: u8, into: &mut String) {
|
|
||||||
match byte {
|
|
||||||
0x21..=0x5B | 0x5D..=0x7D => into.push(byte as char),
|
|
||||||
b'\n' => into.push_str(r"\n"),
|
|
||||||
b'\r' => into.push_str(r"\r"),
|
|
||||||
b'\t' => into.push_str(r"\t"),
|
|
||||||
b'\\' => into.push_str(r"\\"),
|
|
||||||
_ => into.push_str(&format!(r"\x{:02X}", byte)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{escape, unescape};
|
use super::{escape, unescape};
|
||||||
@ -215,7 +101,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn nul() {
|
fn nul() {
|
||||||
assert_eq!(b(b"\x00"), unescape(r"\x00"));
|
assert_eq!(b(b"\x00"), unescape(r"\x00"));
|
||||||
assert_eq!(r"\x00", escape(b"\x00"));
|
assert_eq!(b(b"\x00"), unescape(r"\0"));
|
||||||
|
assert_eq!(r"\0", escape(b"\x00"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
85
crates/cli/src/hostname.rs
Normal file
85
crates/cli/src/hostname.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use std::{ffi::OsString, io};
|
||||||
|
|
||||||
|
/// Returns the hostname of the current system.
|
||||||
|
///
|
||||||
|
/// It is unusual, although technically possible, for this routine to return
|
||||||
|
/// an error. It is difficult to list out the error conditions, but one such
|
||||||
|
/// possibility is platform support.
|
||||||
|
///
|
||||||
|
/// # Platform specific behavior
|
||||||
|
///
|
||||||
|
/// On Windows, this currently uses the "physical DNS hostname" computer name.
|
||||||
|
/// This may change in the future.
|
||||||
|
///
|
||||||
|
/// On Unix, this returns the result of the `gethostname` function from the
|
||||||
|
/// `libc` linked into the program.
|
||||||
|
pub fn hostname() -> io::Result<OsString> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use winapi_util::sysinfo::{get_computer_name, ComputerNameKind};
|
||||||
|
get_computer_name(ComputerNameKind::PhysicalDnsHostname)
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
gethostname()
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
|
{
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"hostname could not be found on unsupported platform",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn gethostname() -> io::Result<OsString> {
|
||||||
|
use std::os::unix::ffi::OsStringExt;
|
||||||
|
|
||||||
|
// SAFETY: There don't appear to be any safety requirements for calling
|
||||||
|
// sysconf.
|
||||||
|
let limit = unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) };
|
||||||
|
if limit == -1 {
|
||||||
|
// It is in theory possible for sysconf to return -1 for a limit but
|
||||||
|
// *not* set errno, in which case, io::Error::last_os_error is
|
||||||
|
// indeterminate. But untangling that is super annoying because std
|
||||||
|
// doesn't expose any unix-specific APIs for inspecting the errno. (We
|
||||||
|
// could do it ourselves, but it just doesn't seem worth doing?)
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
let Ok(maxlen) = usize::try_from(limit) else {
|
||||||
|
let msg = format!("host name max limit ({}) overflowed usize", limit);
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, msg));
|
||||||
|
};
|
||||||
|
// maxlen here includes the NUL terminator.
|
||||||
|
let mut buf = vec![0; maxlen];
|
||||||
|
// SAFETY: The pointer we give is valid as it is derived directly from a
|
||||||
|
// Vec. Similarly, `maxlen` is the length of our Vec, and is thus valid
|
||||||
|
// to write to.
|
||||||
|
let rc = unsafe {
|
||||||
|
libc::gethostname(buf.as_mut_ptr().cast::<libc::c_char>(), maxlen)
|
||||||
|
};
|
||||||
|
if rc == -1 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
// POSIX says that if the hostname is bigger than `maxlen`, then it may
|
||||||
|
// write a truncate name back that is not necessarily NUL terminated (wtf,
|
||||||
|
// lol). So if we can't find a NUL terminator, then just give up.
|
||||||
|
let Some(zeropos) = buf.iter().position(|&b| b == 0) else {
|
||||||
|
let msg = "could not find NUL terminator in hostname";
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, msg));
|
||||||
|
};
|
||||||
|
buf.truncate(zeropos);
|
||||||
|
buf.shrink_to_fit();
|
||||||
|
Ok(OsString::from_vec(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print_hostname() {
|
||||||
|
println!("{:?}", hostname().unwrap());
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,7 @@
|
|||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use std::num::ParseIntError;
|
|
||||||
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
/// An error that occurs when parsing a human readable size description.
|
/// An error that occurs when parsing a human readable size description.
|
||||||
///
|
///
|
||||||
/// This error provides an end user friendly message describing why the
|
/// This error provides an end user friendly message describing why the
|
||||||
/// description coudln't be parsed and what the expected format is.
|
/// description couldn't be parsed and what the expected format is.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct ParseSizeError {
|
pub struct ParseSizeError {
|
||||||
original: String,
|
original: String,
|
||||||
@ -18,7 +11,7 @@ pub struct ParseSizeError {
|
|||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
enum ParseSizeErrorKind {
|
enum ParseSizeErrorKind {
|
||||||
InvalidFormat,
|
InvalidFormat,
|
||||||
InvalidInt(ParseIntError),
|
InvalidInt(std::num::ParseIntError),
|
||||||
Overflow,
|
Overflow,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +23,7 @@ impl ParseSizeError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn int(original: &str, err: ParseIntError) -> ParseSizeError {
|
fn int(original: &str, err: std::num::ParseIntError) -> ParseSizeError {
|
||||||
ParseSizeError {
|
ParseSizeError {
|
||||||
original: original.to_string(),
|
original: original.to_string(),
|
||||||
kind: ParseSizeErrorKind::InvalidInt(err),
|
kind: ParseSizeErrorKind::InvalidInt(err),
|
||||||
@ -45,22 +38,18 @@ impl ParseSizeError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for ParseSizeError {
|
impl std::error::Error for ParseSizeError {}
|
||||||
fn description(&self) -> &str {
|
|
||||||
"invalid size"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ParseSizeError {
|
impl std::fmt::Display for ParseSizeError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use self::ParseSizeErrorKind::*;
|
use self::ParseSizeErrorKind::*;
|
||||||
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
InvalidFormat => write!(
|
InvalidFormat => write!(
|
||||||
f,
|
f,
|
||||||
"invalid format for size '{}', which should be a sequence \
|
"invalid format for size '{}', which should be a non-empty \
|
||||||
of digits followed by an optional 'K', 'M' or 'G' \
|
sequence of digits followed by an optional 'K', 'M' or 'G' \
|
||||||
suffix",
|
suffix",
|
||||||
self.original
|
self.original
|
||||||
),
|
),
|
||||||
InvalidInt(ref err) => write!(
|
InvalidInt(ref err) => write!(
|
||||||
@ -73,9 +62,9 @@ impl fmt::Display for ParseSizeError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseSizeError> for io::Error {
|
impl From<ParseSizeError> for std::io::Error {
|
||||||
fn from(size_err: ParseSizeError) -> io::Error {
|
fn from(size_err: ParseSizeError) -> std::io::Error {
|
||||||
io::Error::new(io::ErrorKind::Other, size_err)
|
std::io::Error::new(std::io::ErrorKind::Other, size_err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,29 +77,24 @@ impl From<ParseSizeError> for io::Error {
|
|||||||
///
|
///
|
||||||
/// Additional suffixes may be added over time.
|
/// Additional suffixes may be added over time.
|
||||||
pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {
|
pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {
|
||||||
lazy_static::lazy_static! {
|
let digits_end =
|
||||||
// Normally I'd just parse something this simple by hand to avoid the
|
size.as_bytes().iter().take_while(|&b| b.is_ascii_digit()).count();
|
||||||
// regex dep, but we bring regex in any way for glob matching, so might
|
let digits = &size[..digits_end];
|
||||||
// as well use it.
|
if digits.is_empty() {
|
||||||
static ref RE: Regex = Regex::new(r"^([0-9]+)([KMG])?$").unwrap();
|
return Err(ParseSizeError::format(size));
|
||||||
}
|
}
|
||||||
|
let value =
|
||||||
|
digits.parse::<u64>().map_err(|e| ParseSizeError::int(size, e))?;
|
||||||
|
|
||||||
let caps = match RE.captures(size) {
|
let suffix = &size[digits_end..];
|
||||||
Some(caps) => caps,
|
if suffix.is_empty() {
|
||||||
None => return Err(ParseSizeError::format(size)),
|
return Ok(value);
|
||||||
};
|
}
|
||||||
let value: u64 =
|
|
||||||
caps[1].parse().map_err(|err| ParseSizeError::int(size, err))?;
|
|
||||||
let suffix = match caps.get(2) {
|
|
||||||
None => return Ok(value),
|
|
||||||
Some(cap) => cap.as_str(),
|
|
||||||
};
|
|
||||||
let bytes = match suffix {
|
let bytes = match suffix {
|
||||||
"K" => value.checked_mul(1 << 10),
|
"K" => value.checked_mul(1 << 10),
|
||||||
"M" => value.checked_mul(1 << 20),
|
"M" => value.checked_mul(1 << 20),
|
||||||
"G" => value.checked_mul(1 << 30),
|
"G" => value.checked_mul(1 << 30),
|
||||||
// Because if the regex matches this group, it must be [KMG].
|
_ => return Err(ParseSizeError::format(size)),
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
};
|
||||||
bytes.ok_or_else(|| ParseSizeError::overflow(size))
|
bytes.ok_or_else(|| ParseSizeError::overflow(size))
|
||||||
}
|
}
|
||||||
|
@ -11,47 +11,26 @@ and Linux.
|
|||||||
|
|
||||||
# Standard I/O
|
# Standard I/O
|
||||||
|
|
||||||
The
|
[`is_readable_stdin`] determines whether stdin can be usefully read from. It
|
||||||
[`is_readable_stdin`](fn.is_readable_stdin.html),
|
is useful when writing an application that changes behavior based on whether
|
||||||
[`is_tty_stderr`](fn.is_tty_stderr.html),
|
the application was invoked with data on stdin. For example, `rg foo` might
|
||||||
[`is_tty_stdin`](fn.is_tty_stdin.html)
|
recursively search the current working directory for occurrences of `foo`, but
|
||||||
and
|
`rg foo < file` might only search the contents of `file`.
|
||||||
[`is_tty_stdout`](fn.is_tty_stdout.html)
|
|
||||||
routines query aspects of standard I/O. `is_readable_stdin` determines whether
|
|
||||||
stdin can be usefully read from, while the `tty` methods determine whether a
|
|
||||||
tty is attached to stdin/stdout/stderr.
|
|
||||||
|
|
||||||
`is_readable_stdin` is useful when writing an application that changes behavior
|
|
||||||
based on whether the application was invoked with data on stdin. For example,
|
|
||||||
`rg foo` might recursively search the current working directory for
|
|
||||||
occurrences of `foo`, but `rg foo < file` might only search the contents of
|
|
||||||
`file`.
|
|
||||||
|
|
||||||
The `tty` methods are useful for similar reasons. Namely, commands like `ls`
|
|
||||||
will change their output depending on whether they are printing to a terminal
|
|
||||||
or not. For example, `ls` shows a file on each line when stdout is redirected
|
|
||||||
to a file or a pipe, but condenses the output to show possibly many files on
|
|
||||||
each line when stdout is connected to a tty.
|
|
||||||
|
|
||||||
|
|
||||||
# Coloring and buffering
|
# Coloring and buffering
|
||||||
|
|
||||||
The
|
The [`stdout`], [`stdout_buffered_block`] and [`stdout_buffered_line`] routines
|
||||||
[`stdout`](fn.stdout.html),
|
are alternative constructors for [`StandardStream`]. A `StandardStream`
|
||||||
[`stdout_buffered_block`](fn.stdout_buffered_block.html)
|
implements `termcolor::WriteColor`, which provides a way to emit colors to
|
||||||
and
|
terminals. Its key use is the encapsulation of buffering style. Namely,
|
||||||
[`stdout_buffered_line`](fn.stdout_buffered_line.html)
|
`stdout` will return a line buffered `StandardStream` if and only if
|
||||||
routines are alternative constructors for
|
stdout is connected to a tty, and will otherwise return a block buffered
|
||||||
[`StandardStream`](struct.StandardStream.html).
|
`StandardStream`. Line buffering is important for use with a tty because it
|
||||||
A `StandardStream` implements `termcolor::WriteColor`, which provides a way
|
typically decreases the latency at which the end user sees output. Block
|
||||||
to emit colors to terminals. Its key use is the encapsulation of buffering
|
buffering is used otherwise because it is faster, and redirecting stdout to a
|
||||||
style. Namely, `stdout` will return a line buffered `StandardStream` if and
|
file typically doesn't benefit from the decreased latency that line buffering
|
||||||
only if stdout is connected to a tty, and will otherwise return a block
|
provides.
|
||||||
buffered `StandardStream`. Line buffering is important for use with a tty
|
|
||||||
because it typically decreases the latency at which the end user sees output.
|
|
||||||
Block buffering is used otherwise because it is faster, and redirecting stdout
|
|
||||||
to a file typically doesn't benefit from the decreased latency that line
|
|
||||||
buffering provides.
|
|
||||||
|
|
||||||
The `stdout_buffered_block` and `stdout_buffered_line` can be used to
|
The `stdout_buffered_block` and `stdout_buffered_line` can be used to
|
||||||
explicitly set the buffering strategy regardless of whether stdout is connected
|
explicitly set the buffering strategy regardless of whether stdout is connected
|
||||||
@ -60,17 +39,12 @@ to a tty or not.
|
|||||||
|
|
||||||
# Escaping
|
# Escaping
|
||||||
|
|
||||||
The
|
The [`escape`](crate::escape()), [`escape_os`], [`unescape`] and
|
||||||
[`escape`](fn.escape.html),
|
[`unescape_os`] routines provide a user friendly way of dealing with UTF-8
|
||||||
[`escape_os`](fn.escape_os.html),
|
encoded strings that can express arbitrary bytes. For example, you might want
|
||||||
[`unescape`](fn.unescape.html)
|
to accept a string containing arbitrary bytes as a command line argument, but
|
||||||
and
|
most interactive shells make such strings difficult to type. Instead, we can
|
||||||
[`unescape_os`](fn.unescape_os.html)
|
ask users to use escape sequences.
|
||||||
routines provide a user friendly way of dealing with UTF-8 encoded strings that
|
|
||||||
can express arbitrary bytes. For example, you might want to accept a string
|
|
||||||
containing arbitrary bytes as a command line argument, but most interactive
|
|
||||||
shells make such strings difficult to type. Instead, we can ask users to use
|
|
||||||
escape sequences.
|
|
||||||
|
|
||||||
For example, `a\xFFz` is itself a valid UTF-8 string corresponding to the
|
For example, `a\xFFz` is itself a valid UTF-8 string corresponding to the
|
||||||
following bytes:
|
following bytes:
|
||||||
@ -103,44 +77,36 @@ makes it easy to show user friendly error messages involving arbitrary bytes.
|
|||||||
# Building patterns
|
# Building patterns
|
||||||
|
|
||||||
Typically, regular expression patterns must be valid UTF-8. However, command
|
Typically, regular expression patterns must be valid UTF-8. However, command
|
||||||
line arguments aren't guaranteed to be valid UTF-8. Unfortunately, the
|
line arguments aren't guaranteed to be valid UTF-8. Unfortunately, the standard
|
||||||
standard library's UTF-8 conversion functions from `OsStr`s do not provide
|
library's UTF-8 conversion functions from `OsStr`s do not provide good error
|
||||||
good error messages. However, the
|
messages. However, the [`pattern_from_bytes`] and [`pattern_from_os`] do,
|
||||||
[`pattern_from_bytes`](fn.pattern_from_bytes.html)
|
including reporting exactly where the first invalid UTF-8 byte is seen.
|
||||||
and
|
|
||||||
[`pattern_from_os`](fn.pattern_from_os.html)
|
|
||||||
do, including reporting exactly where the first invalid UTF-8 byte is seen.
|
|
||||||
|
|
||||||
Additionally, it can be useful to read patterns from a file while reporting
|
Additionally, it can be useful to read patterns from a file while reporting
|
||||||
good error messages that include line numbers. The
|
good error messages that include line numbers. The [`patterns_from_path`],
|
||||||
[`patterns_from_path`](fn.patterns_from_path.html),
|
[`patterns_from_reader`] and [`patterns_from_stdin`] routines do just that. If
|
||||||
[`patterns_from_reader`](fn.patterns_from_reader.html)
|
any pattern is found that is invalid UTF-8, then the error includes the file
|
||||||
and
|
path (if available) along with the line number and the byte offset at which the
|
||||||
[`patterns_from_stdin`](fn.patterns_from_stdin.html)
|
first invalid UTF-8 byte was observed.
|
||||||
routines do just that. If any pattern is found that is invalid UTF-8, then the
|
|
||||||
error includes the file path (if available) along with the line number and the
|
|
||||||
byte offset at which the first invalid UTF-8 byte was observed.
|
|
||||||
|
|
||||||
|
|
||||||
# Read process output
|
# Read process output
|
||||||
|
|
||||||
Sometimes a command line application needs to execute other processes and read
|
Sometimes a command line application needs to execute other processes and
|
||||||
its stdout in a streaming fashion. The
|
read its stdout in a streaming fashion. The [`CommandReader`] provides this
|
||||||
[`CommandReader`](struct.CommandReader.html)
|
functionality with an explicit goal of improving failure modes. In particular,
|
||||||
provides this functionality with an explicit goal of improving failure modes.
|
if the process exits with an error code, then stderr is read and converted into
|
||||||
In particular, if the process exits with an error code, then stderr is read
|
a normal Rust error to show to end users. This makes the underlying failure
|
||||||
and converted into a normal Rust error to show to end users. This makes the
|
modes explicit and gives more information to end users for debugging the
|
||||||
underlying failure modes explicit and gives more information to end users for
|
problem.
|
||||||
debugging the problem.
|
|
||||||
|
|
||||||
As a special case,
|
As a special case, [`DecompressionReader`] provides a way to decompress
|
||||||
[`DecompressionReader`](struct.DecompressionReader.html)
|
arbitrary files by matching their file extensions up with corresponding
|
||||||
provides a way to decompress arbitrary files by matching their file extensions
|
decompression programs (such as `gzip` and `xz`). This is useful as a means of
|
||||||
up with corresponding decompression programs (such as `gzip` and `xz`). This
|
performing simplistic decompression in a portable manner without binding to
|
||||||
is useful as a means of performing simplistic decompression in a portable
|
specific compression libraries. This does come with some overhead though, so
|
||||||
manner without binding to specific compression libraries. This does come with
|
if you need to decompress lots of small files, this may not be an appropriate
|
||||||
some overhead though, so if you need to decompress lots of small files, this
|
convenience to use.
|
||||||
may not be an appropriate convenience to use.
|
|
||||||
|
|
||||||
Each reader has a corresponding builder for additional configuration, such as
|
Each reader has a corresponding builder for additional configuration, such as
|
||||||
whether to read stderr asynchronously in order to avoid deadlock (which is
|
whether to read stderr asynchronously in order to avoid deadlock (which is
|
||||||
@ -149,35 +115,38 @@ enabled by default).
|
|||||||
|
|
||||||
# Miscellaneous parsing
|
# Miscellaneous parsing
|
||||||
|
|
||||||
The
|
The [`parse_human_readable_size`] routine parses strings like `2M` and converts
|
||||||
[`parse_human_readable_size`](fn.parse_human_readable_size.html)
|
them to the corresponding number of bytes (`2 * 1<<20` in this case). If an
|
||||||
routine parses strings like `2M` and converts them to the corresponding number
|
invalid size is found, then a good error message is crafted that typically
|
||||||
of bytes (`2 * 1<<20` in this case). If an invalid size is found, then a good
|
tells the user how to fix the problem.
|
||||||
error message is crafted that typically tells the user how to fix the problem.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
mod decompress;
|
mod decompress;
|
||||||
mod escape;
|
mod escape;
|
||||||
|
mod hostname;
|
||||||
mod human;
|
mod human;
|
||||||
mod pattern;
|
mod pattern;
|
||||||
mod process;
|
mod process;
|
||||||
mod wtr;
|
mod wtr;
|
||||||
|
|
||||||
pub use crate::decompress::{
|
pub use crate::{
|
||||||
resolve_binary, DecompressionMatcher, DecompressionMatcherBuilder,
|
decompress::{
|
||||||
DecompressionReader, DecompressionReaderBuilder,
|
resolve_binary, DecompressionMatcher, DecompressionMatcherBuilder,
|
||||||
};
|
DecompressionReader, DecompressionReaderBuilder,
|
||||||
pub use crate::escape::{escape, escape_os, unescape, unescape_os};
|
},
|
||||||
pub use crate::human::{parse_human_readable_size, ParseSizeError};
|
escape::{escape, escape_os, unescape, unescape_os},
|
||||||
pub use crate::pattern::{
|
hostname::hostname,
|
||||||
pattern_from_bytes, pattern_from_os, patterns_from_path,
|
human::{parse_human_readable_size, ParseSizeError},
|
||||||
patterns_from_reader, patterns_from_stdin, InvalidPatternError,
|
pattern::{
|
||||||
};
|
pattern_from_bytes, pattern_from_os, patterns_from_path,
|
||||||
pub use crate::process::{CommandError, CommandReader, CommandReaderBuilder};
|
patterns_from_reader, patterns_from_stdin, InvalidPatternError,
|
||||||
pub use crate::wtr::{
|
},
|
||||||
stdout, stdout_buffered_block, stdout_buffered_line, StandardStream,
|
process::{CommandError, CommandReader, CommandReaderBuilder},
|
||||||
|
wtr::{
|
||||||
|
stdout, stdout_buffered_block, stdout_buffered_line, StandardStream,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns true if and only if stdin is believed to be readable.
|
/// Returns true if and only if stdin is believed to be readable.
|
||||||
@ -187,38 +156,113 @@ pub use crate::wtr::{
|
|||||||
/// might search the current directory for occurrences of `foo` where as
|
/// might search the current directory for occurrences of `foo` where as
|
||||||
/// `command foo < some-file` or `cat some-file | command foo` might instead
|
/// `command foo < some-file` or `cat some-file | command foo` might instead
|
||||||
/// only search stdin for occurrences of `foo`.
|
/// only search stdin for occurrences of `foo`.
|
||||||
|
///
|
||||||
|
/// Note that this isn't perfect and essentially corresponds to a heuristic.
|
||||||
|
/// When things are unclear (such as if an error occurs during introspection to
|
||||||
|
/// determine whether stdin is readable), this prefers to return `false`. That
|
||||||
|
/// means it's possible for an end user to pipe something into your program and
|
||||||
|
/// have this return `false` and thus potentially lead to ignoring the user's
|
||||||
|
/// stdin data. While not ideal, this is perhaps better than falsely assuming
|
||||||
|
/// stdin is readable, which would result in blocking forever on reading stdin.
|
||||||
|
/// Regardless, commands should always provide explicit fallbacks to override
|
||||||
|
/// behavior. For example, `rg foo -` will explicitly search stdin and `rg foo
|
||||||
|
/// ./` will explicitly search the current working directory.
|
||||||
pub fn is_readable_stdin() -> bool {
|
pub fn is_readable_stdin() -> bool {
|
||||||
|
use std::io::IsTerminal;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn imp() -> bool {
|
fn imp() -> bool {
|
||||||
use same_file::Handle;
|
use std::{
|
||||||
use std::os::unix::fs::FileTypeExt;
|
fs::File,
|
||||||
|
os::{fd::AsFd, unix::fs::FileTypeExt},
|
||||||
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() || ft.is_socket()
|
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
let fd = match stdin.as_fd().try_clone_to_owned() {
|
||||||
|
Ok(fd) => fd,
|
||||||
|
Err(err) => {
|
||||||
|
log::debug!(
|
||||||
|
"for heuristic stdin detection on Unix, \
|
||||||
|
could not clone stdin file descriptor \
|
||||||
|
(thus assuming stdin is not readable): {err}",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let file = File::from(fd);
|
||||||
|
let md = match file.metadata() {
|
||||||
|
Ok(md) => md,
|
||||||
|
Err(err) => {
|
||||||
|
log::debug!(
|
||||||
|
"for heuristic stdin detection on Unix, \
|
||||||
|
could not get file metadata for stdin \
|
||||||
|
(thus assuming stdin is not readable): {err}",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let ft = md.file_type();
|
||||||
|
let is_file = ft.is_file();
|
||||||
|
let is_fifo = ft.is_fifo();
|
||||||
|
let is_socket = ft.is_socket();
|
||||||
|
let is_readable = is_file || is_fifo || is_socket;
|
||||||
|
log::debug!(
|
||||||
|
"for heuristic stdin detection on Unix, \
|
||||||
|
found that \
|
||||||
|
is_file={is_file}, is_fifo={is_fifo} and is_socket={is_socket}, \
|
||||||
|
and thus concluded that is_stdin_readable={is_readable}",
|
||||||
|
);
|
||||||
|
is_readable
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn imp() -> bool {
|
fn imp() -> bool {
|
||||||
use winapi_util as winutil;
|
let stdin = winapi_util::HandleRef::stdin();
|
||||||
|
let typ = match winapi_util::file::typ(stdin) {
|
||||||
winutil::file::typ(winutil::HandleRef::stdin())
|
Ok(typ) => typ,
|
||||||
.map(|t| t.is_disk() || t.is_pipe())
|
Err(err) => {
|
||||||
.unwrap_or(false)
|
log::debug!(
|
||||||
|
"for heuristic stdin detection on Windows, \
|
||||||
|
could not get file type of stdin \
|
||||||
|
(thus assuming stdin is not readable): {err}",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let is_disk = typ.is_disk();
|
||||||
|
let is_pipe = typ.is_pipe();
|
||||||
|
let is_readable = is_disk || is_pipe;
|
||||||
|
log::debug!(
|
||||||
|
"for heuristic stdin detection on Windows, \
|
||||||
|
found that is_disk={is_disk} and is_pipe={is_pipe}, \
|
||||||
|
and thus concluded that is_stdin_readable={is_readable}",
|
||||||
|
);
|
||||||
|
is_readable
|
||||||
}
|
}
|
||||||
|
|
||||||
!is_tty_stdin() && imp()
|
#[cfg(not(any(unix, windows)))]
|
||||||
|
fn imp() -> bool {
|
||||||
|
log::debug!("on non-{{Unix,Windows}}, assuming stdin is not readable");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
!std::io::stdin().is_terminal() && imp()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if stdin is believed to be connectted to a tty
|
/// Returns true if and only if stdin is believed to be connected to a tty
|
||||||
/// or a console.
|
/// or a console.
|
||||||
|
///
|
||||||
|
/// Note that this is now just a wrapper around
|
||||||
|
/// [`std::io::IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html).
|
||||||
|
/// Callers should prefer using the `IsTerminal` trait directly. This routine
|
||||||
|
/// is deprecated and will be removed in the next semver incompatible release.
|
||||||
|
#[deprecated(since = "0.1.10", note = "use std::io::IsTerminal instead")]
|
||||||
pub fn is_tty_stdin() -> bool {
|
pub fn is_tty_stdin() -> bool {
|
||||||
atty::is(atty::Stream::Stdin)
|
use std::io::IsTerminal;
|
||||||
|
std::io::stdin().is_terminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if stdout is believed to be connectted to a tty
|
/// Returns true if and only if stdout is believed to be connected to a tty
|
||||||
/// or a console.
|
/// or a console.
|
||||||
///
|
///
|
||||||
/// This is useful for when you want your command line program to produce
|
/// This is useful for when you want your command line program to produce
|
||||||
@ -226,12 +270,26 @@ pub fn is_tty_stdin() -> bool {
|
|||||||
/// terminal or whether it's being redirected somewhere else. For example,
|
/// terminal or whether it's being redirected somewhere else. For example,
|
||||||
/// implementations of `ls` will often show one item per line when stdout is
|
/// implementations of `ls` will often show one item per line when stdout is
|
||||||
/// redirected, but will condensed output when printing to a tty.
|
/// redirected, but will condensed output when printing to a tty.
|
||||||
|
///
|
||||||
|
/// Note that this is now just a wrapper around
|
||||||
|
/// [`std::io::IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html).
|
||||||
|
/// Callers should prefer using the `IsTerminal` trait directly. This routine
|
||||||
|
/// is deprecated and will be removed in the next semver incompatible release.
|
||||||
|
#[deprecated(since = "0.1.10", note = "use std::io::IsTerminal instead")]
|
||||||
pub fn is_tty_stdout() -> bool {
|
pub fn is_tty_stdout() -> bool {
|
||||||
atty::is(atty::Stream::Stdout)
|
use std::io::IsTerminal;
|
||||||
|
std::io::stdout().is_terminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if stderr is believed to be connectted to a tty
|
/// Returns true if and only if stderr is believed to be connected to a tty
|
||||||
/// or a console.
|
/// or a console.
|
||||||
|
///
|
||||||
|
/// Note that this is now just a wrapper around
|
||||||
|
/// [`std::io::IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html).
|
||||||
|
/// Callers should prefer using the `IsTerminal` trait directly. This routine
|
||||||
|
/// is deprecated and will be removed in the next semver incompatible release.
|
||||||
|
#[deprecated(since = "0.1.10", note = "use std::io::IsTerminal instead")]
|
||||||
pub fn is_tty_stderr() -> bool {
|
pub fn is_tty_stderr() -> bool {
|
||||||
atty::is(atty::Stream::Stderr)
|
use std::io::IsTerminal;
|
||||||
|
std::io::stderr().is_terminal()
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
use std::error;
|
use std::{ffi::OsStr, io, path::Path};
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::fmt;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use bstr::io::BufReadExt;
|
use bstr::io::BufReadExt;
|
||||||
|
|
||||||
@ -28,14 +22,10 @@ impl InvalidPatternError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for InvalidPatternError {
|
impl std::error::Error for InvalidPatternError {}
|
||||||
fn description(&self) -> &str {
|
|
||||||
"invalid pattern"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for InvalidPatternError {
|
impl std::fmt::Display for InvalidPatternError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"found invalid UTF-8 in pattern at byte offset {}: {} \
|
"found invalid UTF-8 in pattern at byte offset {}: {} \
|
||||||
@ -77,7 +67,7 @@ pub fn pattern_from_os(pattern: &OsStr) -> Result<&str, InvalidPatternError> {
|
|||||||
pub fn pattern_from_bytes(
|
pub fn pattern_from_bytes(
|
||||||
pattern: &[u8],
|
pattern: &[u8],
|
||||||
) -> Result<&str, InvalidPatternError> {
|
) -> Result<&str, InvalidPatternError> {
|
||||||
str::from_utf8(pattern).map_err(|err| InvalidPatternError {
|
std::str::from_utf8(pattern).map_err(|err| InvalidPatternError {
|
||||||
original: escape(pattern),
|
original: escape(pattern),
|
||||||
valid_up_to: err.valid_up_to(),
|
valid_up_to: err.valid_up_to(),
|
||||||
})
|
})
|
||||||
@ -91,7 +81,7 @@ pub fn pattern_from_bytes(
|
|||||||
/// path.
|
/// path.
|
||||||
pub fn patterns_from_path<P: AsRef<Path>>(path: P) -> io::Result<Vec<String>> {
|
pub fn patterns_from_path<P: AsRef<Path>>(path: P) -> io::Result<Vec<String>> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let file = File::open(path).map_err(|err| {
|
let file = std::fs::File::open(path).map_err(|err| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
format!("{}: {}", path.display(), err),
|
format!("{}: {}", path.display(), err),
|
||||||
@ -135,7 +125,6 @@ pub fn patterns_from_stdin() -> io::Result<Vec<String>> {
|
|||||||
/// ```
|
/// ```
|
||||||
/// use grep_cli::patterns_from_reader;
|
/// use grep_cli::patterns_from_reader;
|
||||||
///
|
///
|
||||||
/// # fn example() -> Result<(), Box<::std::error::Error>> {
|
|
||||||
/// let patterns = "\
|
/// let patterns = "\
|
||||||
/// foo
|
/// foo
|
||||||
/// bar\\s+foo
|
/// bar\\s+foo
|
||||||
@ -147,7 +136,7 @@ pub fn patterns_from_stdin() -> io::Result<Vec<String>> {
|
|||||||
/// r"bar\s+foo",
|
/// r"bar\s+foo",
|
||||||
/// r"[a-z]{3}",
|
/// r"[a-z]{3}",
|
||||||
/// ]);
|
/// ]);
|
||||||
/// # Ok(()) }
|
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub fn patterns_from_reader<R: io::Read>(rdr: R) -> io::Result<Vec<String>> {
|
pub fn patterns_from_reader<R: io::Read>(rdr: R) -> io::Result<Vec<String>> {
|
||||||
let mut patterns = vec![];
|
let mut patterns = vec![];
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use std::error;
|
use std::{
|
||||||
use std::fmt;
|
io::{self, Read},
|
||||||
use std::io::{self, Read};
|
process,
|
||||||
use std::iter;
|
};
|
||||||
use std::process;
|
|
||||||
use std::thread::{self, JoinHandle};
|
|
||||||
|
|
||||||
/// An error that can occur while running a command and reading its output.
|
/// An error that can occur while running a command and reading its output.
|
||||||
///
|
///
|
||||||
@ -40,14 +38,10 @@ impl CommandError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for CommandError {
|
impl std::error::Error for CommandError {}
|
||||||
fn description(&self) -> &str {
|
|
||||||
"command error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for CommandError {
|
impl std::fmt::Display for CommandError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
CommandErrorKind::Io(ref e) => e.fmt(f),
|
CommandErrorKind::Io(ref e) => e.fmt(f),
|
||||||
CommandErrorKind::Stderr(ref bytes) => {
|
CommandErrorKind::Stderr(ref bytes) => {
|
||||||
@ -55,7 +49,7 @@ impl fmt::Display for CommandError {
|
|||||||
if msg.trim().is_empty() {
|
if msg.trim().is_empty() {
|
||||||
write!(f, "<stderr is empty>")
|
write!(f, "<stderr is empty>")
|
||||||
} else {
|
} else {
|
||||||
let div = iter::repeat('-').take(79).collect::<String>();
|
let div = "-".repeat(79);
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"\n{div}\n{msg}\n{div}",
|
"\n{div}\n{msg}\n{div}",
|
||||||
@ -161,18 +155,17 @@ impl CommandReaderBuilder {
|
|||||||
/// is returned as an error.
|
/// is returned as an error.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use std::io::Read;
|
/// use std::{io::Read, process::Command};
|
||||||
/// use std::process::Command;
|
///
|
||||||
/// use grep_cli::CommandReader;
|
/// use grep_cli::CommandReader;
|
||||||
///
|
///
|
||||||
/// # fn example() -> Result<(), Box<::std::error::Error>> {
|
|
||||||
/// let mut cmd = Command::new("gzip");
|
/// let mut cmd = Command::new("gzip");
|
||||||
/// cmd.arg("-d").arg("-c").arg("/usr/share/man/man1/ls.1.gz");
|
/// cmd.arg("-d").arg("-c").arg("/usr/share/man/man1/ls.1.gz");
|
||||||
///
|
///
|
||||||
/// let mut rdr = CommandReader::new(&mut cmd)?;
|
/// let mut rdr = CommandReader::new(&mut cmd)?;
|
||||||
/// let mut contents = vec![];
|
/// let mut contents = vec![];
|
||||||
/// rdr.read_to_end(&mut contents)?;
|
/// rdr.read_to_end(&mut contents)?;
|
||||||
/// # Ok(()) }
|
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CommandReader {
|
pub struct CommandReader {
|
||||||
@ -198,8 +191,7 @@ impl CommandReader {
|
|||||||
/// returned.
|
/// returned.
|
||||||
///
|
///
|
||||||
/// If the caller requires additional configuration for the reader
|
/// If the caller requires additional configuration for the reader
|
||||||
/// returned, then use
|
/// returned, then use [`CommandReaderBuilder`].
|
||||||
/// [`CommandReaderBuilder`](struct.CommandReaderBuilder.html).
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cmd: &mut process::Command,
|
cmd: &mut process::Command,
|
||||||
) -> Result<CommandReader, CommandError> {
|
) -> Result<CommandReader, CommandError> {
|
||||||
@ -221,7 +213,7 @@ impl CommandReader {
|
|||||||
///
|
///
|
||||||
/// `close` is also called in `drop` as a last line of defense against
|
/// `close` is also called in `drop` as a last line of defense against
|
||||||
/// resource leakage. Any error from the child process is then printed as a
|
/// resource leakage. Any error from the child process is then printed as a
|
||||||
/// warning to stderr. This can be avoided by explictly calling `close`
|
/// warning to stderr. This can be avoided by explicitly calling `close`
|
||||||
/// before the CommandReader is dropped.
|
/// before the CommandReader is dropped.
|
||||||
pub fn close(&mut self) -> io::Result<()> {
|
pub fn close(&mut self) -> io::Result<()> {
|
||||||
// Dropping stdout closes the underlying file descriptor, which should
|
// Dropping stdout closes the underlying file descriptor, which should
|
||||||
@ -279,7 +271,7 @@ impl io::Read for CommandReader {
|
|||||||
/// stderr.
|
/// stderr.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum StderrReader {
|
enum StderrReader {
|
||||||
Async(Option<JoinHandle<CommandError>>),
|
Async(Option<std::thread::JoinHandle<CommandError>>),
|
||||||
Sync(process::ChildStderr),
|
Sync(process::ChildStderr),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +279,7 @@ impl StderrReader {
|
|||||||
/// Create a reader for stderr that reads contents asynchronously.
|
/// Create a reader for stderr that reads contents asynchronously.
|
||||||
fn r#async(mut stderr: process::ChildStderr) -> StderrReader {
|
fn r#async(mut stderr: process::ChildStderr) -> StderrReader {
|
||||||
let handle =
|
let handle =
|
||||||
thread::spawn(move || stderr_to_command_error(&mut stderr));
|
std::thread::spawn(move || stderr_to_command_error(&mut stderr));
|
||||||
StderrReader::Async(Some(handle))
|
StderrReader::Async(Some(handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use std::io;
|
use std::io::{self, IsTerminal};
|
||||||
|
|
||||||
use termcolor;
|
use termcolor::HyperlinkSpec;
|
||||||
|
|
||||||
use crate::is_tty_stdout;
|
|
||||||
|
|
||||||
/// A writer that supports coloring with either line or block buffering.
|
/// A writer that supports coloring with either line or block buffering.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct StandardStream(StandardStreamKind);
|
pub struct StandardStream(StandardStreamKind);
|
||||||
|
|
||||||
/// Returns a possibly buffered writer to stdout for the given color choice.
|
/// Returns a possibly buffered writer to stdout for the given color choice.
|
||||||
@ -22,7 +21,7 @@ pub struct StandardStream(StandardStreamKind);
|
|||||||
/// The color choice given is passed along to the underlying writer. To
|
/// The color choice given is passed along to the underlying writer. To
|
||||||
/// completely disable colors in all cases, use `ColorChoice::Never`.
|
/// completely disable colors in all cases, use `ColorChoice::Never`.
|
||||||
pub fn stdout(color_choice: termcolor::ColorChoice) -> StandardStream {
|
pub fn stdout(color_choice: termcolor::ColorChoice) -> StandardStream {
|
||||||
if is_tty_stdout() {
|
if std::io::stdout().is_terminal() {
|
||||||
stdout_buffered_line(color_choice)
|
stdout_buffered_line(color_choice)
|
||||||
} else {
|
} else {
|
||||||
stdout_buffered_block(color_choice)
|
stdout_buffered_block(color_choice)
|
||||||
@ -35,10 +34,8 @@ pub fn stdout(color_choice: termcolor::ColorChoice) -> StandardStream {
|
|||||||
/// users see output as soon as it's written. The downside of this approach
|
/// users see output as soon as it's written. The downside of this approach
|
||||||
/// is that it can be slower, especially when there is a lot of output.
|
/// is that it can be slower, especially when there is a lot of output.
|
||||||
///
|
///
|
||||||
/// You might consider using
|
/// You might consider using [`stdout`] instead, which chooses the buffering
|
||||||
/// [`stdout`](fn.stdout.html)
|
/// strategy automatically based on whether stdout is connected to a tty.
|
||||||
/// instead, which chooses the buffering strategy automatically based on
|
|
||||||
/// whether stdout is connected to a tty.
|
|
||||||
pub fn stdout_buffered_line(
|
pub fn stdout_buffered_line(
|
||||||
color_choice: termcolor::ColorChoice,
|
color_choice: termcolor::ColorChoice,
|
||||||
) -> StandardStream {
|
) -> StandardStream {
|
||||||
@ -52,10 +49,8 @@ pub fn stdout_buffered_line(
|
|||||||
/// the cost of writing data. The downside of this approach is that it can
|
/// the cost of writing data. The downside of this approach is that it can
|
||||||
/// increase the latency of display output when writing to a tty.
|
/// increase the latency of display output when writing to a tty.
|
||||||
///
|
///
|
||||||
/// You might consider using
|
/// You might consider using [`stdout`] instead, which chooses the buffering
|
||||||
/// [`stdout`](fn.stdout.html)
|
/// strategy automatically based on whether stdout is connected to a tty.
|
||||||
/// instead, which chooses the buffering strategy automatically based on
|
|
||||||
/// whether stdout is connected to a tty.
|
|
||||||
pub fn stdout_buffered_block(
|
pub fn stdout_buffered_block(
|
||||||
color_choice: termcolor::ColorChoice,
|
color_choice: termcolor::ColorChoice,
|
||||||
) -> StandardStream {
|
) -> StandardStream {
|
||||||
@ -63,6 +58,7 @@ pub fn stdout_buffered_block(
|
|||||||
StandardStream(StandardStreamKind::BlockBuffered(out))
|
StandardStream(StandardStreamKind::BlockBuffered(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum StandardStreamKind {
|
enum StandardStreamKind {
|
||||||
LineBuffered(termcolor::StandardStream),
|
LineBuffered(termcolor::StandardStream),
|
||||||
BlockBuffered(termcolor::BufferedStandardStream),
|
BlockBuffered(termcolor::BufferedStandardStream),
|
||||||
@ -101,6 +97,16 @@ impl termcolor::WriteColor for StandardStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn supports_hyperlinks(&self) -> bool {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref w) => w.supports_hyperlinks(),
|
||||||
|
BlockBuffered(ref w) => w.supports_hyperlinks(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_color(&mut self, spec: &termcolor::ColorSpec) -> io::Result<()> {
|
fn set_color(&mut self, spec: &termcolor::ColorSpec) -> io::Result<()> {
|
||||||
use self::StandardStreamKind::*;
|
use self::StandardStreamKind::*;
|
||||||
@ -111,6 +117,16 @@ impl termcolor::WriteColor for StandardStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_hyperlink(&mut self, link: &HyperlinkSpec) -> io::Result<()> {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref mut w) => w.set_hyperlink(link),
|
||||||
|
BlockBuffered(ref mut w) => w.set_hyperlink(link),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn reset(&mut self) -> io::Result<()> {
|
fn reset(&mut self) -> io::Result<()> {
|
||||||
use self::StandardStreamKind::*;
|
use self::StandardStreamKind::*;
|
||||||
|
3109
crates/core/app.rs
3109
crates/core/app.rs
File diff suppressed because it is too large
Load Diff
1873
crates/core/args.rs
1873
crates/core/args.rs
File diff suppressed because it is too large
Load Diff
107
crates/core/flags/complete/bash.rs
Normal file
107
crates/core/flags/complete/bash.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*!
|
||||||
|
Provides completions for ripgrep's CLI for the bash shell.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::flags::defs::FLAGS;
|
||||||
|
|
||||||
|
const TEMPLATE_FULL: &'static str = "
|
||||||
|
_rg() {
|
||||||
|
local i cur prev opts cmds
|
||||||
|
COMPREPLY=()
|
||||||
|
cur=\"${COMP_WORDS[COMP_CWORD]}\"
|
||||||
|
prev=\"${COMP_WORDS[COMP_CWORD-1]}\"
|
||||||
|
cmd=\"\"
|
||||||
|
opts=\"\"
|
||||||
|
|
||||||
|
for i in ${COMP_WORDS[@]}; do
|
||||||
|
case \"${i}\" in
|
||||||
|
rg)
|
||||||
|
cmd=\"rg\"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
case \"${cmd}\" in
|
||||||
|
rg)
|
||||||
|
opts=\"!OPTS!\"
|
||||||
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||||
|
COMPREPLY=($(compgen -W \"${opts}\" -- \"${cur}\"))
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case \"${prev}\" in
|
||||||
|
!CASES!
|
||||||
|
esac
|
||||||
|
COMPREPLY=($(compgen -W \"${opts}\" -- \"${cur}\"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _rg -o bashdefault -o default rg
|
||||||
|
";
|
||||||
|
|
||||||
|
const TEMPLATE_CASE: &'static str = "
|
||||||
|
!FLAG!)
|
||||||
|
COMPREPLY=($(compgen -f \"${cur}\"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
";
|
||||||
|
|
||||||
|
const TEMPLATE_CASE_CHOICES: &'static str = "
|
||||||
|
!FLAG!)
|
||||||
|
COMPREPLY=($(compgen -W \"!CHOICES!\" -- \"${cur}\"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
";
|
||||||
|
|
||||||
|
/// Generate completions for Bash.
|
||||||
|
///
|
||||||
|
/// Note that these completions are based on what was produced for ripgrep <=13
|
||||||
|
/// using Clap 2.x. Improvements on this are welcome.
|
||||||
|
pub(crate) fn generate() -> String {
|
||||||
|
let mut opts = String::new();
|
||||||
|
for flag in FLAGS.iter() {
|
||||||
|
opts.push_str("--");
|
||||||
|
opts.push_str(flag.name_long());
|
||||||
|
opts.push(' ');
|
||||||
|
if let Some(short) = flag.name_short() {
|
||||||
|
opts.push('-');
|
||||||
|
opts.push(char::from(short));
|
||||||
|
opts.push(' ');
|
||||||
|
}
|
||||||
|
if let Some(name) = flag.name_negated() {
|
||||||
|
opts.push_str("--");
|
||||||
|
opts.push_str(name);
|
||||||
|
opts.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.push_str("<PATTERN> <PATH>...");
|
||||||
|
|
||||||
|
let mut cases = String::new();
|
||||||
|
for flag in FLAGS.iter() {
|
||||||
|
let template = if !flag.doc_choices().is_empty() {
|
||||||
|
let choices = flag.doc_choices().join(" ");
|
||||||
|
TEMPLATE_CASE_CHOICES.trim_end().replace("!CHOICES!", &choices)
|
||||||
|
} else {
|
||||||
|
TEMPLATE_CASE.trim_end().to_string()
|
||||||
|
};
|
||||||
|
let name = format!("--{}", flag.name_long());
|
||||||
|
cases.push_str(&template.replace("!FLAG!", &name));
|
||||||
|
if let Some(short) = flag.name_short() {
|
||||||
|
let name = format!("-{}", char::from(short));
|
||||||
|
cases.push_str(&template.replace("!FLAG!", &name));
|
||||||
|
}
|
||||||
|
if let Some(negated) = flag.name_negated() {
|
||||||
|
let name = format!("--{negated}");
|
||||||
|
cases.push_str(&template.replace("!FLAG!", &name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEMPLATE_FULL
|
||||||
|
.replace("!OPTS!", &opts)
|
||||||
|
.replace("!CASES!", &cases)
|
||||||
|
.trim_start()
|
||||||
|
.to_string()
|
||||||
|
}
|
29
crates/core/flags/complete/encodings.sh
Normal file
29
crates/core/flags/complete/encodings.sh
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# This is impossible to read, but these encodings rarely if ever change, so
|
||||||
|
# it probably does not matter. They are derived from the list given here:
|
||||||
|
# https://encoding.spec.whatwg.org/#concept-encoding-get
|
||||||
|
#
|
||||||
|
# The globbing here works in both fish and zsh (though they expand it in
|
||||||
|
# different orders). It may work in other shells too.
|
||||||
|
|
||||||
|
{{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean}
|
||||||
|
logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian}
|
||||||
|
866 ibm{819,866} csibm866
|
||||||
|
big5{,-hkscs} {cn-,cs}big5 x-x-big5
|
||||||
|
cp{819,866,125{0,1,2,3,4,5,6,7,8}} x-cp125{0,1,2,3,4,5,6,7,8}
|
||||||
|
csiso2022{jp,kr} csiso8859{6,8}{e,i}
|
||||||
|
csisolatin{1,2,3,4,5,6,9} csisolatin{arabic,cyrillic,greek,hebrew}
|
||||||
|
ecma-{114,118} asmo-708 elot_928 sun_eu_greek
|
||||||
|
euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese
|
||||||
|
{,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312
|
||||||
|
iso-2022-{cn,cn-ext,jp,kr}
|
||||||
|
iso8859{,-}{1,2,3,4,5,6,7,8,9,10,11,13,14,15}
|
||||||
|
iso-8859-{1,2,3,4,5,6,7,8,9,10,11,{6,8}-{e,i},13,14,15,16} iso_8859-{1,2,3,4,5,6,7,8,9,15}
|
||||||
|
iso_8859-{1,2,6,7}:1987 iso_8859-{3,4,5,8}:1988 iso_8859-9:1989
|
||||||
|
iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157}
|
||||||
|
koi{,8,8-r,8-ru,8-u,8_r} cskoi8r
|
||||||
|
ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987
|
||||||
|
latin{1,2,3,4,5,6} l{1,2,3,4,5,6,9}
|
||||||
|
shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932
|
||||||
|
utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8
|
||||||
|
windows-{31j,874,949,125{0,1,2,3,4,5,6,7,8}} dos-874 tis-620 ansi_x3.4-1968
|
||||||
|
x-user-defined auto none
|
68
crates/core/flags/complete/fish.rs
Normal file
68
crates/core/flags/complete/fish.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*!
|
||||||
|
Provides completions for ripgrep's CLI for the fish shell.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::flags::{defs::FLAGS, CompletionType};
|
||||||
|
|
||||||
|
const TEMPLATE: &'static str = "complete -c rg !SHORT! -l !LONG! -d '!DOC!'";
|
||||||
|
const TEMPLATE_NEGATED: &'static str =
|
||||||
|
"complete -c rg -l !NEGATED! -n '__fish_contains_opt !SHORT! !LONG!' -d '!DOC!'\n";
|
||||||
|
|
||||||
|
/// Generate completions for Fish.
|
||||||
|
pub(crate) fn generate() -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
for flag in FLAGS.iter() {
|
||||||
|
let short = match flag.name_short() {
|
||||||
|
None => "".to_string(),
|
||||||
|
Some(byte) => format!("-s {}", char::from(byte)),
|
||||||
|
};
|
||||||
|
let long = flag.name_long();
|
||||||
|
let doc = flag.doc_short().replace("'", "\\'");
|
||||||
|
let mut completion = TEMPLATE
|
||||||
|
.replace("!SHORT!", &short)
|
||||||
|
.replace("!LONG!", &long)
|
||||||
|
.replace("!DOC!", &doc);
|
||||||
|
|
||||||
|
match flag.completion_type() {
|
||||||
|
CompletionType::Filename => {
|
||||||
|
completion.push_str(" -r -F");
|
||||||
|
}
|
||||||
|
CompletionType::Executable => {
|
||||||
|
completion.push_str(" -r -f -a '(__fish_complete_command)'");
|
||||||
|
}
|
||||||
|
CompletionType::Filetype => {
|
||||||
|
completion.push_str(
|
||||||
|
" -r -f -a '(rg --type-list | string replace : \\t)'",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
CompletionType::Encoding => {
|
||||||
|
completion.push_str(" -r -f -a '");
|
||||||
|
completion.push_str(super::ENCODINGS);
|
||||||
|
completion.push_str("'");
|
||||||
|
}
|
||||||
|
CompletionType::Other if !flag.doc_choices().is_empty() => {
|
||||||
|
completion.push_str(" -r -f -a '");
|
||||||
|
completion.push_str(&flag.doc_choices().join(" "));
|
||||||
|
completion.push_str("'");
|
||||||
|
}
|
||||||
|
CompletionType::Other if !flag.is_switch() => {
|
||||||
|
completion.push_str(" -r -f");
|
||||||
|
}
|
||||||
|
CompletionType::Other => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
completion.push('\n');
|
||||||
|
out.push_str(&completion);
|
||||||
|
|
||||||
|
if let Some(negated) = flag.name_negated() {
|
||||||
|
out.push_str(
|
||||||
|
&TEMPLATE_NEGATED
|
||||||
|
.replace("!NEGATED!", &negated)
|
||||||
|
.replace("!SHORT!", &short)
|
||||||
|
.replace("!LONG!", &long)
|
||||||
|
.replace("!DOC!", &doc),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
10
crates/core/flags/complete/mod.rs
Normal file
10
crates/core/flags/complete/mod.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/*!
|
||||||
|
Modules for generating completions for various shells.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static ENCODINGS: &'static str = include_str!("encodings.sh");
|
||||||
|
|
||||||
|
pub(super) mod bash;
|
||||||
|
pub(super) mod fish;
|
||||||
|
pub(super) mod powershell;
|
||||||
|
pub(super) mod zsh;
|
86
crates/core/flags/complete/powershell.rs
Normal file
86
crates/core/flags/complete/powershell.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*!
|
||||||
|
Provides completions for ripgrep's CLI for PowerShell.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::flags::defs::FLAGS;
|
||||||
|
|
||||||
|
const TEMPLATE: &'static str = "
|
||||||
|
using namespace System.Management.Automation
|
||||||
|
using namespace System.Management.Automation.Language
|
||||||
|
|
||||||
|
Register-ArgumentCompleter -Native -CommandName 'rg' -ScriptBlock {
|
||||||
|
param($wordToComplete, $commandAst, $cursorPosition)
|
||||||
|
$commandElements = $commandAst.CommandElements
|
||||||
|
$command = @(
|
||||||
|
'rg'
|
||||||
|
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
||||||
|
$element = $commandElements[$i]
|
||||||
|
if ($element -isnot [StringConstantExpressionAst] -or
|
||||||
|
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||||
|
$element.Value.StartsWith('-')) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
$element.Value
|
||||||
|
}) -join ';'
|
||||||
|
|
||||||
|
$completions = @(switch ($command) {
|
||||||
|
'rg' {
|
||||||
|
!FLAGS!
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$completions.Where{ $_.CompletionText -like \"$wordToComplete*\" } |
|
||||||
|
Sort-Object -Property ListItemText
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
const TEMPLATE_FLAG: &'static str =
|
||||||
|
"[CompletionResult]::new('!DASH_NAME!', '!NAME!', [CompletionResultType]::ParameterName, '!DOC!')";
|
||||||
|
|
||||||
|
/// Generate completions for PowerShell.
|
||||||
|
///
|
||||||
|
/// Note that these completions are based on what was produced for ripgrep <=13
|
||||||
|
/// using Clap 2.x. Improvements on this are welcome.
|
||||||
|
pub(crate) fn generate() -> String {
|
||||||
|
let mut flags = String::new();
|
||||||
|
for (i, flag) in FLAGS.iter().enumerate() {
|
||||||
|
let doc = flag.doc_short().replace("'", "''");
|
||||||
|
|
||||||
|
let dash_name = format!("--{}", flag.name_long());
|
||||||
|
let name = flag.name_long();
|
||||||
|
if i > 0 {
|
||||||
|
flags.push('\n');
|
||||||
|
}
|
||||||
|
flags.push_str(" ");
|
||||||
|
flags.push_str(
|
||||||
|
&TEMPLATE_FLAG
|
||||||
|
.replace("!DASH_NAME!", &dash_name)
|
||||||
|
.replace("!NAME!", &name)
|
||||||
|
.replace("!DOC!", &doc),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(byte) = flag.name_short() {
|
||||||
|
let dash_name = format!("-{}", char::from(byte));
|
||||||
|
let name = char::from(byte).to_string();
|
||||||
|
flags.push_str("\n ");
|
||||||
|
flags.push_str(
|
||||||
|
&TEMPLATE_FLAG
|
||||||
|
.replace("!DASH_NAME!", &dash_name)
|
||||||
|
.replace("!NAME!", &name)
|
||||||
|
.replace("!DOC!", &doc),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(negated) = flag.name_negated() {
|
||||||
|
let dash_name = format!("--{}", negated);
|
||||||
|
flags.push_str("\n ");
|
||||||
|
flags.push_str(
|
||||||
|
&TEMPLATE_FLAG
|
||||||
|
.replace("!DASH_NAME!", &dash_name)
|
||||||
|
.replace("!NAME!", &negated)
|
||||||
|
.replace("!DOC!", &doc),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TEMPLATE.trim_start().replace("!FLAGS!", &flags)
|
||||||
|
}
|
@ -30,7 +30,7 @@ _rg() {
|
|||||||
[[ $_RG_COMPLETE_LIST_ARGS == (1|t*|y*) ]] ||
|
[[ $_RG_COMPLETE_LIST_ARGS == (1|t*|y*) ]] ||
|
||||||
# (--[imnp]* => --ignore*, --messages, --no-*, --pcre2-unicode)
|
# (--[imnp]* => --ignore*, --messages, --no-*, --pcre2-unicode)
|
||||||
[[ $PREFIX$SUFFIX == --[imnp]* ]] ||
|
[[ $PREFIX$SUFFIX == --[imnp]* ]] ||
|
||||||
zstyle -t ":complete:$curcontext:*" complete-all
|
zstyle -t ":completion:${curcontext}:" complete-all
|
||||||
then
|
then
|
||||||
no=
|
no=
|
||||||
fi
|
fi
|
||||||
@ -73,6 +73,7 @@ _rg() {
|
|||||||
{-c,--count}'[only show count of matching lines for each file]'
|
{-c,--count}'[only show count of matching lines for each file]'
|
||||||
'--count-matches[only show count of individual matches for each file]'
|
'--count-matches[only show count of individual matches for each file]'
|
||||||
'--include-zero[include files with zero matches in summary]'
|
'--include-zero[include files with zero matches in summary]'
|
||||||
|
$no"--no-include-zero[don't include files with zero matches in summary]"
|
||||||
|
|
||||||
+ '(encoding)' # Encoding options
|
+ '(encoding)' # Encoding options
|
||||||
{-E+,--encoding=}'[specify text encoding of files to search]: :_rg_encodings'
|
{-E+,--encoding=}'[specify text encoding of files to search]: :_rg_encodings'
|
||||||
@ -108,6 +109,15 @@ _rg() {
|
|||||||
{-L,--follow}'[follow symlinks]'
|
{-L,--follow}'[follow symlinks]'
|
||||||
$no"--no-follow[don't follow symlinks]"
|
$no"--no-follow[don't follow symlinks]"
|
||||||
|
|
||||||
|
+ '(generate)' # Options for generating ancillary data
|
||||||
|
'--generate=[generate man page or completion scripts]:when:((
|
||||||
|
man\:"man page"
|
||||||
|
complete-bash\:"shell completions for bash"
|
||||||
|
complete-zsh\:"shell completions for zsh"
|
||||||
|
complete-fish\:"shell completions for fish"
|
||||||
|
complete-powershell\:"shell completions for PowerShell"
|
||||||
|
))'
|
||||||
|
|
||||||
+ glob # File-glob options
|
+ glob # File-glob options
|
||||||
'*'{-g+,--glob=}'[include/exclude files matching specified glob]:glob'
|
'*'{-g+,--glob=}'[include/exclude files matching specified glob]:glob'
|
||||||
'*--iglob=[include/exclude files matching specified case-insensitive glob]:glob'
|
'*--iglob=[include/exclude files matching specified case-insensitive glob]:glob'
|
||||||
@ -125,8 +135,8 @@ _rg() {
|
|||||||
$no"--no-hidden[don't search hidden files and directories]"
|
$no"--no-hidden[don't search hidden files and directories]"
|
||||||
|
|
||||||
+ '(hybrid)' # hybrid regex options
|
+ '(hybrid)' # hybrid regex options
|
||||||
'--auto-hybrid-regex[dynamically use PCRE2 if necessary]'
|
'--auto-hybrid-regex[DEPRECATED: dynamically use PCRE2 if necessary]'
|
||||||
$no"--no-auto-hybrid-regex[don't dynamically use PCRE2 if necessary]"
|
$no"--no-auto-hybrid-regex[DEPRECATED: don't dynamically use PCRE2 if necessary]"
|
||||||
|
|
||||||
+ '(ignore)' # Ignore-file options
|
+ '(ignore)' # Ignore-file options
|
||||||
"(--no-ignore-global --no-ignore-parent --no-ignore-vcs --no-ignore-dot)--no-ignore[don't respect ignore files]"
|
"(--no-ignore-global --no-ignore-parent --no-ignore-vcs --no-ignore-dot)--no-ignore[don't respect ignore files]"
|
||||||
@ -182,7 +192,8 @@ _rg() {
|
|||||||
$no"--no-max-columns-preview[don't show preview for long lines (with -M)]"
|
$no"--no-max-columns-preview[don't show preview for long lines (with -M)]"
|
||||||
|
|
||||||
+ '(max-depth)' # Directory-depth options
|
+ '(max-depth)' # Directory-depth options
|
||||||
'--max-depth=[specify max number of directories to descend]:number of directories'
|
{-d,--max-depth}'[specify max number of directories to descend]:number of directories'
|
||||||
|
'--maxdepth=[alias for --max-depth]:number of directories'
|
||||||
'!--maxdepth=:number of directories'
|
'!--maxdepth=:number of directories'
|
||||||
|
|
||||||
+ '(messages)' # Error-message options
|
+ '(messages)' # Error-message options
|
||||||
@ -210,15 +221,15 @@ _rg() {
|
|||||||
|
|
||||||
+ '(passthru)' # Pass-through options
|
+ '(passthru)' # Pass-through options
|
||||||
'(--vimgrep)--passthru[show both matching and non-matching lines]'
|
'(--vimgrep)--passthru[show both matching and non-matching lines]'
|
||||||
'!(--vimgrep)--passthrough'
|
'(--vimgrep)--passthrough[alias for --passthru]'
|
||||||
|
|
||||||
+ '(pcre2)' # PCRE2 options
|
+ '(pcre2)' # PCRE2 options
|
||||||
{-P,--pcre2}'[enable matching with PCRE2]'
|
{-P,--pcre2}'[enable matching with PCRE2]'
|
||||||
$no'(pcre2-unicode)--no-pcre2[disable matching with PCRE2]'
|
$no'(pcre2-unicode)--no-pcre2[disable matching with PCRE2]'
|
||||||
|
|
||||||
+ '(pcre2-unicode)' # PCRE2 Unicode options
|
+ '(pcre2-unicode)' # PCRE2 Unicode options
|
||||||
$no'(--no-pcre2 --no-pcre2-unicode)--pcre2-unicode[enable PCRE2 Unicode mode (with -P)]'
|
$no'(--no-pcre2 --no-pcre2-unicode)--pcre2-unicode[DEPRECATED: enable PCRE2 Unicode mode (with -P)]'
|
||||||
'(--no-pcre2 --pcre2-unicode)--no-pcre2-unicode[disable PCRE2 Unicode mode (with -P)]'
|
'(--no-pcre2 --pcre2-unicode)--no-pcre2-unicode[DEPRECATED: disable PCRE2 Unicode mode (with -P)]'
|
||||||
|
|
||||||
+ '(pre)' # Preprocessing options
|
+ '(pre)' # Preprocessing options
|
||||||
'(-z --search-zip)--pre=[specify preprocessor utility]:preprocessor utility:_command_names -e'
|
'(-z --search-zip)--pre=[specify preprocessor utility]:preprocessor utility:_command_names -e'
|
||||||
@ -252,7 +263,8 @@ _rg() {
|
|||||||
accessed\:"sort by last accessed time"
|
accessed\:"sort by last accessed time"
|
||||||
created\:"sort by creation time"
|
created\:"sort by creation time"
|
||||||
))'
|
))'
|
||||||
'!(threads)--sort-files[sort results by file path (disables parallelism)]'
|
'(threads)--sort-files[DEPRECATED: sort results by file path (disables parallelism)]'
|
||||||
|
$no"--no-sort-files[DEPRECATED: do not sort results]"
|
||||||
|
|
||||||
+ '(stats)' # Statistics options
|
+ '(stats)' # Statistics options
|
||||||
'(--files file-match)--stats[show search statistics]'
|
'(--files file-match)--stats[show search statistics]'
|
||||||
@ -293,6 +305,7 @@ _rg() {
|
|||||||
|
|
||||||
+ misc # Other options — no need to separate these at the moment
|
+ misc # Other options — no need to separate these at the moment
|
||||||
'(-b --byte-offset)'{-b,--byte-offset}'[show 0-based byte offset for each matching line]'
|
'(-b --byte-offset)'{-b,--byte-offset}'[show 0-based byte offset for each matching line]'
|
||||||
|
$no"--no-byte-offset[don't show byte offsets for each matching line]"
|
||||||
'--color=[specify when to use colors in output]:when:((
|
'--color=[specify when to use colors in output]:when:((
|
||||||
never\:"never use colors"
|
never\:"never use colors"
|
||||||
auto\:"use colors or not based on stdout, TERM, etc."
|
auto\:"use colors or not based on stdout, TERM, etc."
|
||||||
@ -305,11 +318,14 @@ _rg() {
|
|||||||
'--debug[show debug messages]'
|
'--debug[show debug messages]'
|
||||||
'--field-context-separator[set string to delimit fields in context lines]'
|
'--field-context-separator[set string to delimit fields in context lines]'
|
||||||
'--field-match-separator[set string to delimit fields in matching lines]'
|
'--field-match-separator[set string to delimit fields in matching lines]'
|
||||||
|
'--hostname-bin=[executable for getting system hostname]:hostname executable:_command_names -e'
|
||||||
|
'--hyperlink-format=[specify pattern for hyperlinks]:pattern'
|
||||||
'--trace[show more verbose debug messages]'
|
'--trace[show more verbose debug messages]'
|
||||||
'--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size (bytes)'
|
'--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size (bytes)'
|
||||||
"(1 stats)--files[show each file that would be searched (but don't search)]"
|
"(1 stats)--files[show each file that would be searched (but don't search)]"
|
||||||
'*--ignore-file=[specify additional ignore file]:ignore file:_files'
|
'*--ignore-file=[specify additional ignore file]:ignore file:_files'
|
||||||
'(-v --invert-match)'{-v,--invert-match}'[invert matching]'
|
'(-v --invert-match)'{-v,--invert-match}'[invert matching]'
|
||||||
|
$no"--no-invert-match[do not invert matching]"
|
||||||
'(-M --max-columns)'{-M+,--max-columns=}'[specify max length of lines to print]:number of bytes'
|
'(-M --max-columns)'{-M+,--max-columns=}'[specify max length of lines to print]:number of bytes'
|
||||||
'(-m --max-count)'{-m+,--max-count=}'[specify max number of matches per file]:number of matches'
|
'(-m --max-count)'{-m+,--max-count=}'[specify max number of matches per file]:number of matches'
|
||||||
'--max-filesize=[specify size above which files should be ignored]:file size (bytes)'
|
'--max-filesize=[specify size above which files should be ignored]:file size (bytes)'
|
||||||
@ -319,6 +335,7 @@ _rg() {
|
|||||||
'(-q --quiet)'{-q,--quiet}'[suppress normal output]'
|
'(-q --quiet)'{-q,--quiet}'[suppress normal output]'
|
||||||
'--regex-size-limit=[specify upper size limit of compiled regex]:regex size (bytes)'
|
'--regex-size-limit=[specify upper size limit of compiled regex]:regex size (bytes)'
|
||||||
'*'{-u,--unrestricted}'[reduce level of "smart" searching]'
|
'*'{-u,--unrestricted}'[reduce level of "smart" searching]'
|
||||||
|
'--stop-on-nonmatch[stop on first non-matching line after a matching one]'
|
||||||
|
|
||||||
+ operand # Operands
|
+ operand # Operands
|
||||||
'(--files --type-list file regexp)1: :_guard "^-*" pattern'
|
'(--files --type-list file regexp)1: :_guard "^-*" pattern'
|
||||||
@ -396,32 +413,8 @@ _rg_encodings() {
|
|||||||
local -a expl
|
local -a expl
|
||||||
local -aU _encodings
|
local -aU _encodings
|
||||||
|
|
||||||
# This is impossible to read, but these encodings rarely if ever change, so it
|
|
||||||
# probably doesn't matter. They are derived from the list given here:
|
|
||||||
# https://encoding.spec.whatwg.org/#concept-encoding-get
|
|
||||||
_encodings=(
|
_encodings=(
|
||||||
{{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean}
|
!ENCODINGS!
|
||||||
logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian}
|
|
||||||
866 ibm{819,866} csibm866
|
|
||||||
big5{,-hkscs} {cn-,cs}big5 x-x-big5
|
|
||||||
cp{819,866,125{0..8}} x-cp125{0..8}
|
|
||||||
csiso2022{jp,kr} csiso8859{6,8}{e,i}
|
|
||||||
csisolatin{{1..6},9} csisolatin{arabic,cyrillic,greek,hebrew}
|
|
||||||
ecma-{114,118} asmo-708 elot_928 sun_eu_greek
|
|
||||||
euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese
|
|
||||||
{,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312
|
|
||||||
iso-2022-{cn,cn-ext,jp,kr}
|
|
||||||
iso8859{,-}{{1..11},13,14,15}
|
|
||||||
iso-8859-{{1..11},{6,8}-{e,i},13,14,15,16} iso_8859-{{1..9},15}
|
|
||||||
iso_8859-{1,2,6,7}:1987 iso_8859-{3,4,5,8}:1988 iso_8859-9:1989
|
|
||||||
iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157}
|
|
||||||
koi{,8,8-r,8-ru,8-u,8_r} cskoi8r
|
|
||||||
ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987
|
|
||||||
latin{1..6} l{{1..6},9}
|
|
||||||
shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932
|
|
||||||
utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8
|
|
||||||
windows-{31j,874,949,125{0..8}} dos-874 tis-620 ansi_x3.4-1968
|
|
||||||
x-user-defined auto none
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_wanted encodings expl encoding compadd -a "$@" - _encodings
|
_wanted encodings expl encoding compadd -a "$@" - _encodings
|
||||||
@ -432,12 +425,24 @@ _rg_types() {
|
|||||||
local -a expl
|
local -a expl
|
||||||
local -aU _types
|
local -aU _types
|
||||||
|
|
||||||
_types=( ${(@)${(f)"$( _call_program types rg --type-list )"}%%:*} )
|
_types=( ${(@)${(f)"$( _call_program types $words[1] --type-list )"}//:[[:space:]]##/:} )
|
||||||
|
|
||||||
_wanted types expl 'file type' compadd -a "$@" - _types
|
if zstyle -t ":completion:${curcontext}:types" extra-verbose; then
|
||||||
|
_describe -t types 'file type' _types
|
||||||
|
else
|
||||||
|
_wanted types expl 'file type' compadd "$@" - ${(@)_types%%:*}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_rg "$@"
|
# Don't run the completion function when being sourced by itself.
|
||||||
|
#
|
||||||
|
# See https://github.com/BurntSushi/ripgrep/issues/2956
|
||||||
|
# See https://github.com/BurntSushi/ripgrep/pull/2957
|
||||||
|
if [[ $funcstack[1] == _rg ]] || (( ! $+functions[compdef] )); then
|
||||||
|
_rg "$@"
|
||||||
|
else
|
||||||
|
compdef _rg rg
|
||||||
|
fi
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# ZSH COMPLETION REFERENCE
|
# ZSH COMPLETION REFERENCE
|
23
crates/core/flags/complete/zsh.rs
Normal file
23
crates/core/flags/complete/zsh.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*!
|
||||||
|
Provides completions for ripgrep's CLI for the zsh shell.
|
||||||
|
|
||||||
|
Unlike completion short for other shells (at time of writing), zsh's
|
||||||
|
completions for ripgrep are maintained by hand. This is because:
|
||||||
|
|
||||||
|
1. They are lovingly written by an expert in such things.
|
||||||
|
2. Are much higher in quality than the ones below that are auto-generated.
|
||||||
|
Namely, the zsh completions take application level context about flag
|
||||||
|
compatibility into account.
|
||||||
|
3. There is a CI script that fails if a new flag is added to ripgrep that
|
||||||
|
isn't included in the zsh completions.
|
||||||
|
4. There is a wealth of documentation in the zsh script explaining how it
|
||||||
|
works and how it can be extended.
|
||||||
|
|
||||||
|
In principle, I'd be open to maintaining any completion script by hand so
|
||||||
|
long as it meets criteria 3 and 4 above.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Generate completions for zsh.
|
||||||
|
pub(crate) fn generate() -> String {
|
||||||
|
include_str!("rg.zsh").replace("!ENCODINGS!", super::ENCODINGS.trim_end())
|
||||||
|
}
|
@ -1,22 +1,20 @@
|
|||||||
// This module provides routines for reading ripgrep config "rc" files. The
|
/*!
|
||||||
// primary output of these routines is a sequence of arguments, where each
|
This module provides routines for reading ripgrep config "rc" files.
|
||||||
// argument corresponds precisely to one shell argument.
|
|
||||||
|
|
||||||
use std::env;
|
The primary output of these routines is a sequence of arguments, where each
|
||||||
use std::error::Error;
|
argument corresponds precisely to one shell argument.
|
||||||
use std::ffi::OsString;
|
*/
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
use std::{
|
||||||
use std::path::{Path, PathBuf};
|
ffi::OsString,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use bstr::{io::BufReadExt, ByteSlice};
|
use bstr::{io::BufReadExt, ByteSlice};
|
||||||
use log;
|
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
/// Return a sequence of arguments derived from ripgrep rc configuration files.
|
/// Return a sequence of arguments derived from ripgrep rc configuration files.
|
||||||
pub fn args() -> Vec<OsString> {
|
pub fn args() -> Vec<OsString> {
|
||||||
let config_path = match env::var_os("RIPGREP_CONFIG_PATH") {
|
let config_path = match std::env::var_os("RIPGREP_CONFIG_PATH") {
|
||||||
None => return vec![],
|
None => return vec![],
|
||||||
Some(config_path) => {
|
Some(config_path) => {
|
||||||
if config_path.is_empty() {
|
if config_path.is_empty() {
|
||||||
@ -28,7 +26,10 @@ pub fn args() -> Vec<OsString> {
|
|||||||
let (args, errs) = match parse(&config_path) {
|
let (args, errs) = match parse(&config_path) {
|
||||||
Ok((args, errs)) => (args, errs),
|
Ok((args, errs)) => (args, errs),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
message!("{}", err);
|
message!(
|
||||||
|
"failed to read the file specified in RIPGREP_CONFIG_PATH: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -55,11 +56,11 @@ pub fn args() -> Vec<OsString> {
|
|||||||
/// for each line in addition to successfully parsed arguments.
|
/// for each line in addition to successfully parsed arguments.
|
||||||
fn parse<P: AsRef<Path>>(
|
fn parse<P: AsRef<Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<(Vec<OsString>, Vec<Box<dyn Error>>)> {
|
) -> anyhow::Result<(Vec<OsString>, Vec<anyhow::Error>)> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
match File::open(&path) {
|
match std::fs::File::open(&path) {
|
||||||
Ok(file) => parse_reader(file),
|
Ok(file) => parse_reader(file),
|
||||||
Err(err) => Err(From::from(format!("{}: {}", path.display(), err))),
|
Err(err) => anyhow::bail!("{}: {}", path.display(), err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,10 +75,10 @@ fn parse<P: AsRef<Path>>(
|
|||||||
/// If the reader could not be read, then an error is returned. If there was a
|
/// If the reader could not be read, then an error is returned. If there was a
|
||||||
/// problem parsing one or more lines, then errors are returned for each line
|
/// problem parsing one or more lines, then errors are returned for each line
|
||||||
/// in addition to successfully parsed arguments.
|
/// in addition to successfully parsed arguments.
|
||||||
fn parse_reader<R: io::Read>(
|
fn parse_reader<R: std::io::Read>(
|
||||||
rdr: R,
|
rdr: R,
|
||||||
) -> Result<(Vec<OsString>, Vec<Box<dyn Error>>)> {
|
) -> anyhow::Result<(Vec<OsString>, Vec<anyhow::Error>)> {
|
||||||
let bufrdr = io::BufReader::new(rdr);
|
let mut bufrdr = std::io::BufReader::new(rdr);
|
||||||
let (mut args, mut errs) = (vec![], vec![]);
|
let (mut args, mut errs) = (vec![], vec![]);
|
||||||
let mut line_number = 0;
|
let mut line_number = 0;
|
||||||
bufrdr.for_byte_line_with_terminator(|line| {
|
bufrdr.for_byte_line_with_terminator(|line| {
|
||||||
@ -92,7 +93,7 @@ fn parse_reader<R: io::Read>(
|
|||||||
args.push(osstr.to_os_string());
|
args.push(osstr.to_os_string());
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
errs.push(format!("{}: {}", line_number, err).into());
|
errs.push(anyhow::anyhow!("{line_number}: {err}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
7675
crates/core/flags/defs.rs
Normal file
7675
crates/core/flags/defs.rs
Normal file
File diff suppressed because it is too large
Load Diff
259
crates/core/flags/doc/help.rs
Normal file
259
crates/core/flags/doc/help.rs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/*!
|
||||||
|
Provides routines for generating ripgrep's "short" and "long" help
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
The short version is used when the `-h` flag is given, while the long version
|
||||||
|
is used when the `--help` flag is given.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{collections::BTreeMap, fmt::Write};
|
||||||
|
|
||||||
|
use crate::flags::{defs::FLAGS, doc::version, Category, Flag};
|
||||||
|
|
||||||
|
const TEMPLATE_SHORT: &'static str = include_str!("template.short.help");
|
||||||
|
const TEMPLATE_LONG: &'static str = include_str!("template.long.help");
|
||||||
|
|
||||||
|
/// Wraps `std::write!` and asserts there is no failure.
|
||||||
|
///
|
||||||
|
/// We only write to `String` in this module.
|
||||||
|
macro_rules! write {
|
||||||
|
($($tt:tt)*) => { std::write!($($tt)*).unwrap(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate short documentation, i.e., for `-h`.
|
||||||
|
pub(crate) fn generate_short() -> String {
|
||||||
|
let mut cats: BTreeMap<Category, (Vec<String>, Vec<String>)> =
|
||||||
|
BTreeMap::new();
|
||||||
|
let (mut maxcol1, mut maxcol2) = (0, 0);
|
||||||
|
for flag in FLAGS.iter().copied() {
|
||||||
|
let columns =
|
||||||
|
cats.entry(flag.doc_category()).or_insert((vec![], vec![]));
|
||||||
|
let (col1, col2) = generate_short_flag(flag);
|
||||||
|
maxcol1 = maxcol1.max(col1.len());
|
||||||
|
maxcol2 = maxcol2.max(col2.len());
|
||||||
|
columns.0.push(col1);
|
||||||
|
columns.1.push(col2);
|
||||||
|
}
|
||||||
|
let mut out =
|
||||||
|
TEMPLATE_SHORT.replace("!!VERSION!!", &version::generate_digits());
|
||||||
|
for (cat, (col1, col2)) in cats.iter() {
|
||||||
|
let var = format!("!!{name}!!", name = cat.as_str());
|
||||||
|
let val = format_short_columns(col1, col2, maxcol1, maxcol2);
|
||||||
|
out = out.replace(&var, &val);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate short for a single flag.
|
||||||
|
///
|
||||||
|
/// The first element corresponds to the flag name while the second element
|
||||||
|
/// corresponds to the documentation string.
|
||||||
|
fn generate_short_flag(flag: &dyn Flag) -> (String, String) {
|
||||||
|
let (mut col1, mut col2) = (String::new(), String::new());
|
||||||
|
|
||||||
|
// Some of the variable names are fine for longer form
|
||||||
|
// docs, but they make the succinct short help very noisy.
|
||||||
|
// So just shorten some of them.
|
||||||
|
let var = flag.doc_variable().map(|s| {
|
||||||
|
let mut s = s.to_string();
|
||||||
|
s = s.replace("SEPARATOR", "SEP");
|
||||||
|
s = s.replace("REPLACEMENT", "TEXT");
|
||||||
|
s = s.replace("NUM+SUFFIX?", "NUM");
|
||||||
|
s
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate the first column, the flag name.
|
||||||
|
if let Some(byte) = flag.name_short() {
|
||||||
|
let name = char::from(byte);
|
||||||
|
write!(col1, r"-{name}");
|
||||||
|
write!(col1, r", ");
|
||||||
|
}
|
||||||
|
write!(col1, r"--{name}", name = flag.name_long());
|
||||||
|
if let Some(var) = var.as_ref() {
|
||||||
|
write!(col1, r"={var}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// And now the second column, with the description.
|
||||||
|
write!(col2, "{}", flag.doc_short());
|
||||||
|
|
||||||
|
(col1, col2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write two columns of documentation.
|
||||||
|
///
|
||||||
|
/// `maxcol1` should be the maximum length (in bytes) of the first column,
|
||||||
|
/// while `maxcol2` should be the maximum length (in bytes) of the second
|
||||||
|
/// column.
|
||||||
|
fn format_short_columns(
|
||||||
|
col1: &[String],
|
||||||
|
col2: &[String],
|
||||||
|
maxcol1: usize,
|
||||||
|
_maxcol2: usize,
|
||||||
|
) -> String {
|
||||||
|
assert_eq!(col1.len(), col2.len(), "columns must have equal length");
|
||||||
|
const PAD: usize = 2;
|
||||||
|
let mut out = String::new();
|
||||||
|
for (i, (c1, c2)) in col1.iter().zip(col2.iter()).enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(out, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let pad = maxcol1 - c1.len() + PAD;
|
||||||
|
write!(out, " ");
|
||||||
|
write!(out, "{c1}");
|
||||||
|
write!(out, "{}", " ".repeat(pad));
|
||||||
|
write!(out, "{c2}");
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate long documentation, i.e., for `--help`.
|
||||||
|
pub(crate) fn generate_long() -> String {
|
||||||
|
let mut cats = BTreeMap::new();
|
||||||
|
for flag in FLAGS.iter().copied() {
|
||||||
|
let mut cat = cats.entry(flag.doc_category()).or_insert(String::new());
|
||||||
|
if !cat.is_empty() {
|
||||||
|
write!(cat, "\n\n");
|
||||||
|
}
|
||||||
|
generate_long_flag(flag, &mut cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out =
|
||||||
|
TEMPLATE_LONG.replace("!!VERSION!!", &version::generate_digits());
|
||||||
|
for (cat, value) in cats.iter() {
|
||||||
|
let var = format!("!!{name}!!", name = cat.as_str());
|
||||||
|
out = out.replace(&var, value);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write generated documentation for `flag` to `out`.
|
||||||
|
fn generate_long_flag(flag: &dyn Flag, out: &mut String) {
|
||||||
|
if let Some(byte) = flag.name_short() {
|
||||||
|
let name = char::from(byte);
|
||||||
|
write!(out, r" -{name}");
|
||||||
|
if let Some(var) = flag.doc_variable() {
|
||||||
|
write!(out, r" {var}");
|
||||||
|
}
|
||||||
|
write!(out, r", ");
|
||||||
|
} else {
|
||||||
|
write!(out, r" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = flag.name_long();
|
||||||
|
write!(out, r"--{name}");
|
||||||
|
if let Some(var) = flag.doc_variable() {
|
||||||
|
write!(out, r"={var}");
|
||||||
|
}
|
||||||
|
write!(out, "\n");
|
||||||
|
|
||||||
|
let doc = flag.doc_long().trim();
|
||||||
|
let doc = super::render_custom_markup(doc, "flag", |name, out| {
|
||||||
|
let Some(flag) = crate::flags::parse::lookup(name) else {
|
||||||
|
unreachable!(r"found unrecognized \flag{{{name}}} in --help docs")
|
||||||
|
};
|
||||||
|
if let Some(name) = flag.name_short() {
|
||||||
|
write!(out, r"-{}/", char::from(name));
|
||||||
|
}
|
||||||
|
write!(out, r"--{}", flag.name_long());
|
||||||
|
});
|
||||||
|
let doc = super::render_custom_markup(&doc, "flag-negate", |name, out| {
|
||||||
|
let Some(flag) = crate::flags::parse::lookup(name) else {
|
||||||
|
unreachable!(
|
||||||
|
r"found unrecognized \flag-negate{{{name}}} in --help docs"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let Some(name) = flag.name_negated() else {
|
||||||
|
let long = flag.name_long();
|
||||||
|
unreachable!(
|
||||||
|
"found \\flag-negate{{{long}}} in --help docs but \
|
||||||
|
{long} does not have a negation"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
write!(out, r"--{name}");
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut cleaned = remove_roff(&doc);
|
||||||
|
if let Some(negated) = flag.name_negated() {
|
||||||
|
// Flags that can be negated that aren't switches, like
|
||||||
|
// --context-separator, are somewhat weird. Because of that, the docs
|
||||||
|
// for those flags should discuss the semantics of negation explicitly.
|
||||||
|
// But for switches, the behavior is always the same.
|
||||||
|
if flag.is_switch() {
|
||||||
|
write!(cleaned, "\n\nThis flag can be disabled with --{negated}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let indent = " ".repeat(8);
|
||||||
|
let wrapopts = textwrap::Options::new(71)
|
||||||
|
// Normally I'd be fine with breaking at hyphens, but ripgrep's docs
|
||||||
|
// includes a lot of flag names, and they in turn contain hyphens.
|
||||||
|
// Breaking flag names across lines is not great.
|
||||||
|
.word_splitter(textwrap::WordSplitter::NoHyphenation);
|
||||||
|
for (i, paragraph) in cleaned.split("\n\n").enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(out, "\n\n");
|
||||||
|
}
|
||||||
|
let mut new = paragraph.to_string();
|
||||||
|
if paragraph.lines().all(|line| line.starts_with(" ")) {
|
||||||
|
// Re-indent but don't refill so as to preserve line breaks
|
||||||
|
// in code/shell example snippets.
|
||||||
|
new = textwrap::indent(&new, &indent);
|
||||||
|
} else {
|
||||||
|
new = new.replace("\n", " ");
|
||||||
|
new = textwrap::refill(&new, &wrapopts);
|
||||||
|
new = textwrap::indent(&new, &indent);
|
||||||
|
}
|
||||||
|
write!(out, "{}", new.trim_end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes roff syntax from `v` such that the result is approximately plain
|
||||||
|
/// text readable.
|
||||||
|
///
|
||||||
|
/// This is basically a mish mash of heuristics based on the specific roff used
|
||||||
|
/// in the docs for the flags in this tool. If new kinds of roff are used in
|
||||||
|
/// the docs, then this may need to be updated to handle them.
|
||||||
|
fn remove_roff(v: &str) -> String {
|
||||||
|
let mut lines = vec![];
|
||||||
|
for line in v.trim().lines() {
|
||||||
|
assert!(!line.is_empty(), "roff should have no empty lines");
|
||||||
|
if line.starts_with(".") {
|
||||||
|
if line.starts_with(".IP ") {
|
||||||
|
let item_label = line
|
||||||
|
.split(" ")
|
||||||
|
.nth(1)
|
||||||
|
.expect("first argument to .IP")
|
||||||
|
.replace(r"\(bu", r"•")
|
||||||
|
.replace(r"\fB", "")
|
||||||
|
.replace(r"\fP", ":");
|
||||||
|
lines.push(format!("{item_label}"));
|
||||||
|
} else if line.starts_with(".IB ") || line.starts_with(".BI ") {
|
||||||
|
let pieces = line
|
||||||
|
.split_whitespace()
|
||||||
|
.skip(1)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.concat();
|
||||||
|
lines.push(format!("{pieces}"));
|
||||||
|
} else if line.starts_with(".sp")
|
||||||
|
|| line.starts_with(".PP")
|
||||||
|
|| line.starts_with(".TP")
|
||||||
|
{
|
||||||
|
lines.push("".to_string());
|
||||||
|
}
|
||||||
|
} else if line.starts_with(r"\fB") && line.ends_with(r"\fP") {
|
||||||
|
let line = line.replace(r"\fB", "").replace(r"\fP", "");
|
||||||
|
lines.push(format!("{line}:"));
|
||||||
|
} else {
|
||||||
|
lines.push(line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Squash multiple adjacent paragraph breaks into one.
|
||||||
|
lines.dedup_by(|l1, l2| l1.is_empty() && l2.is_empty());
|
||||||
|
lines
|
||||||
|
.join("\n")
|
||||||
|
.replace(r"\fB", "")
|
||||||
|
.replace(r"\fI", "")
|
||||||
|
.replace(r"\fP", "")
|
||||||
|
.replace(r"\-", "-")
|
||||||
|
.replace(r"\\", r"\")
|
||||||
|
}
|
110
crates/core/flags/doc/man.rs
Normal file
110
crates/core/flags/doc/man.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*!
|
||||||
|
Provides routines for generating ripgrep's man page in `roff` format.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{collections::BTreeMap, fmt::Write};
|
||||||
|
|
||||||
|
use crate::flags::{defs::FLAGS, doc::version, Flag};
|
||||||
|
|
||||||
|
const TEMPLATE: &'static str = include_str!("template.rg.1");
|
||||||
|
|
||||||
|
/// Wraps `std::write!` and asserts there is no failure.
|
||||||
|
///
|
||||||
|
/// We only write to `String` in this module.
|
||||||
|
macro_rules! write {
|
||||||
|
($($tt:tt)*) => { std::write!($($tt)*).unwrap(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `std::writeln!` and asserts there is no failure.
|
||||||
|
///
|
||||||
|
/// We only write to `String` in this module.
|
||||||
|
macro_rules! writeln {
|
||||||
|
($($tt:tt)*) => { std::writeln!($($tt)*).unwrap(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `roff` formatted string corresponding to ripgrep's entire man
|
||||||
|
/// page.
|
||||||
|
pub(crate) fn generate() -> String {
|
||||||
|
let mut cats = BTreeMap::new();
|
||||||
|
for flag in FLAGS.iter().copied() {
|
||||||
|
let mut cat = cats.entry(flag.doc_category()).or_insert(String::new());
|
||||||
|
if !cat.is_empty() {
|
||||||
|
writeln!(cat, ".sp");
|
||||||
|
}
|
||||||
|
generate_flag(flag, &mut cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = TEMPLATE.replace("!!VERSION!!", &version::generate_digits());
|
||||||
|
for (cat, value) in cats.iter() {
|
||||||
|
let var = format!("!!{name}!!", name = cat.as_str());
|
||||||
|
out = out.replace(&var, value);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `roff` formatted documentation for `flag` to `out`.
|
||||||
|
fn generate_flag(flag: &'static dyn Flag, out: &mut String) {
|
||||||
|
if let Some(byte) = flag.name_short() {
|
||||||
|
let name = char::from(byte);
|
||||||
|
write!(out, r"\fB\-{name}\fP");
|
||||||
|
if let Some(var) = flag.doc_variable() {
|
||||||
|
write!(out, r" \fI{var}\fP");
|
||||||
|
}
|
||||||
|
write!(out, r", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = flag.name_long();
|
||||||
|
write!(out, r"\fB\-\-{name}\fP");
|
||||||
|
if let Some(var) = flag.doc_variable() {
|
||||||
|
write!(out, r"=\fI{var}\fP");
|
||||||
|
}
|
||||||
|
write!(out, "\n");
|
||||||
|
|
||||||
|
writeln!(out, ".RS 4");
|
||||||
|
let doc = flag.doc_long().trim();
|
||||||
|
// Convert \flag{foo} into something nicer.
|
||||||
|
let doc = super::render_custom_markup(doc, "flag", |name, out| {
|
||||||
|
let Some(flag) = crate::flags::parse::lookup(name) else {
|
||||||
|
unreachable!(r"found unrecognized \flag{{{name}}} in roff docs")
|
||||||
|
};
|
||||||
|
out.push_str(r"\fB");
|
||||||
|
if let Some(name) = flag.name_short() {
|
||||||
|
write!(out, r"\-{}/", char::from(name));
|
||||||
|
}
|
||||||
|
write!(out, r"\-\-{}", flag.name_long());
|
||||||
|
out.push_str(r"\fP");
|
||||||
|
});
|
||||||
|
// Convert \flag-negate{foo} into something nicer.
|
||||||
|
let doc = super::render_custom_markup(&doc, "flag-negate", |name, out| {
|
||||||
|
let Some(flag) = crate::flags::parse::lookup(name) else {
|
||||||
|
unreachable!(
|
||||||
|
r"found unrecognized \flag-negate{{{name}}} in roff docs"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let Some(name) = flag.name_negated() else {
|
||||||
|
let long = flag.name_long();
|
||||||
|
unreachable!(
|
||||||
|
"found \\flag-negate{{{long}}} in roff docs but \
|
||||||
|
{long} does not have a negation"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
out.push_str(r"\fB");
|
||||||
|
write!(out, r"\-\-{name}");
|
||||||
|
out.push_str(r"\fP");
|
||||||
|
});
|
||||||
|
writeln!(out, "{doc}");
|
||||||
|
if let Some(negated) = flag.name_negated() {
|
||||||
|
// Flags that can be negated that aren't switches, like
|
||||||
|
// --context-separator, are somewhat weird. Because of that, the docs
|
||||||
|
// for those flags should discuss the semantics of negation explicitly.
|
||||||
|
// But for switches, the behavior is always the same.
|
||||||
|
if flag.is_switch() {
|
||||||
|
writeln!(out, ".sp");
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
r"This flag can be disabled with \fB\-\-{negated}\fP."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(out, ".RE");
|
||||||
|
}
|
38
crates/core/flags/doc/mod.rs
Normal file
38
crates/core/flags/doc/mod.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*!
|
||||||
|
Modules for generating documentation for ripgrep's flags.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub(crate) mod help;
|
||||||
|
pub(crate) mod man;
|
||||||
|
pub(crate) mod version;
|
||||||
|
|
||||||
|
/// Searches for `\tag{...}` occurrences in `doc` and calls `replacement` for
|
||||||
|
/// each such tag found.
|
||||||
|
///
|
||||||
|
/// The first argument given to `replacement` is the tag value, `...`. The
|
||||||
|
/// second argument is the buffer that accumulates the full replacement text.
|
||||||
|
///
|
||||||
|
/// Since this function is only intended to be used on doc strings written into
|
||||||
|
/// the program source code, callers should panic in `replacement` if there are
|
||||||
|
/// any errors or unexpected circumstances.
|
||||||
|
fn render_custom_markup(
|
||||||
|
mut doc: &str,
|
||||||
|
tag: &str,
|
||||||
|
mut replacement: impl FnMut(&str, &mut String),
|
||||||
|
) -> String {
|
||||||
|
let mut out = String::with_capacity(doc.len());
|
||||||
|
let tag_prefix = format!(r"\{tag}{{");
|
||||||
|
while let Some(offset) = doc.find(&tag_prefix) {
|
||||||
|
out.push_str(&doc[..offset]);
|
||||||
|
|
||||||
|
let start = offset + tag_prefix.len();
|
||||||
|
let Some(end) = doc[start..].find('}').map(|i| start + i) else {
|
||||||
|
unreachable!(r"found {tag_prefix} without closing }}");
|
||||||
|
};
|
||||||
|
let name = &doc[start..end];
|
||||||
|
replacement(name, &mut out);
|
||||||
|
doc = &doc[end + 1..];
|
||||||
|
}
|
||||||
|
out.push_str(doc);
|
||||||
|
out
|
||||||
|
}
|
61
crates/core/flags/doc/template.long.help
Normal file
61
crates/core/flags/doc/template.long.help
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
ripgrep !!VERSION!!
|
||||||
|
Andrew Gallant <jamslam@gmail.com>
|
||||||
|
|
||||||
|
ripgrep (rg) recursively searches the current directory for lines matching
|
||||||
|
a regex pattern. By default, ripgrep will respect gitignore rules and
|
||||||
|
automatically skip hidden files/directories and binary files.
|
||||||
|
|
||||||
|
Use -h for short descriptions and --help for more details.
|
||||||
|
|
||||||
|
Project home page: https://github.com/BurntSushi/ripgrep
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
rg [OPTIONS] PATTERN [PATH ...]
|
||||||
|
rg [OPTIONS] -e PATTERN ... [PATH ...]
|
||||||
|
rg [OPTIONS] -f PATTERNFILE ... [PATH ...]
|
||||||
|
rg [OPTIONS] --files [PATH ...]
|
||||||
|
rg [OPTIONS] --type-list
|
||||||
|
command | rg [OPTIONS] PATTERN
|
||||||
|
rg [OPTIONS] --help
|
||||||
|
rg [OPTIONS] --version
|
||||||
|
|
||||||
|
POSITIONAL ARGUMENTS:
|
||||||
|
<PATTERN>
|
||||||
|
A regular expression used for searching. To match a pattern beginning
|
||||||
|
with a dash, use the -e/--regexp flag.
|
||||||
|
|
||||||
|
For example, to search for the literal '-foo', you can use this flag:
|
||||||
|
|
||||||
|
rg -e -foo
|
||||||
|
|
||||||
|
You can also use the special '--' delimiter to indicate that no more
|
||||||
|
flags will be provided. Namely, the following is equivalent to the
|
||||||
|
above:
|
||||||
|
|
||||||
|
rg -- -foo
|
||||||
|
|
||||||
|
<PATH>...
|
||||||
|
A file or directory to search. Directories are searched recursively.
|
||||||
|
File paths specified on the command line override glob and ignore
|
||||||
|
rules.
|
||||||
|
|
||||||
|
INPUT OPTIONS:
|
||||||
|
!!input!!
|
||||||
|
|
||||||
|
SEARCH OPTIONS:
|
||||||
|
!!search!!
|
||||||
|
|
||||||
|
FILTER OPTIONS:
|
||||||
|
!!filter!!
|
||||||
|
|
||||||
|
OUTPUT OPTIONS:
|
||||||
|
!!output!!
|
||||||
|
|
||||||
|
OUTPUT MODES:
|
||||||
|
!!output-modes!!
|
||||||
|
|
||||||
|
LOGGING OPTIONS:
|
||||||
|
!!logging!!
|
||||||
|
|
||||||
|
OTHER BEHAVIORS:
|
||||||
|
!!other-behaviors!!
|
424
crates/core/flags/doc/template.rg.1
Normal file
424
crates/core/flags/doc/template.rg.1
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
.TH RG 1 2024-09-08 "!!VERSION!!" "User Commands"
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH NAME
|
||||||
|
rg \- recursively search the current directory for lines matching a pattern
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.\" I considered using GNU troff's .SY and .YS "synopsis" macros here, but it
|
||||||
|
.\" looks like they aren't portable. Specifically, they don't appear to be in
|
||||||
|
.\" BSD's mdoc used on macOS.
|
||||||
|
.sp
|
||||||
|
\fBrg\fP [\fIOPTIONS\fP] \fIPATTERN\fP [\fIPATH\fP...]
|
||||||
|
.sp
|
||||||
|
\fBrg\fP [\fIOPTIONS\fP] \fB\-e\fP \fIPATTERN\fP... [\fIPATH\fP...]
|
||||||
|
.sp
|
||||||
|
\fBrg\fP [\fIOPTIONS\fP] \fB\-f\fP \fIPATTERNFILE\fP... [\fIPATH\fP...]
|
||||||
|
.sp
|
||||||
|
\fBrg\fP [\fIOPTIONS\fP] \fB\-\-files\fP [\fIPATH\fP...]
|
||||||
|
.sp
|
||||||
|
\fBrg\fP [\fIOPTIONS\fP] \fB\-\-type\-list\fP
|
||||||
|
.sp
|
||||||
|
\fIcommand\fP | \fBrg\fP [\fIOPTIONS\fP] \fIPATTERN\fP
|
||||||
|
.sp
|
||||||
|
\fBrg\fP [\fIOPTIONS\fP] \fB\-\-help\fP
|
||||||
|
.sp
|
||||||
|
\fBrg\fP [\fIOPTIONS\fP] \fB\-\-version\fP
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH DESCRIPTION
|
||||||
|
ripgrep (rg) recursively searches the current directory for a regex pattern.
|
||||||
|
By default, ripgrep will respect your \fB.gitignore\fP and automatically skip
|
||||||
|
hidden files/directories and binary files.
|
||||||
|
.sp
|
||||||
|
ripgrep's default regex engine uses finite automata and guarantees linear
|
||||||
|
time searching. Because of this, features like backreferences and arbitrary
|
||||||
|
look-around are not supported. However, if ripgrep is built with PCRE2,
|
||||||
|
then the \fB\-P/\-\-pcre2\fP flag can be used to enable backreferences and
|
||||||
|
look-around.
|
||||||
|
.sp
|
||||||
|
ripgrep supports configuration files. Set \fBRIPGREP_CONFIG_PATH\fP to a
|
||||||
|
configuration file. The file can specify one shell argument per line. Lines
|
||||||
|
starting with \fB#\fP are ignored. For more details, see \fBCONFIGURATION
|
||||||
|
FILES\fP below.
|
||||||
|
.sp
|
||||||
|
ripgrep will automatically detect if stdin is a readable file and search stdin
|
||||||
|
for a regex pattern, e.g. \fBls | rg foo\fP. In some environments, stdin may
|
||||||
|
exist when it shouldn't. To turn off stdin detection, one can explicitly
|
||||||
|
specify the directory to search, e.g. \fBrg foo ./\fP.
|
||||||
|
.sp
|
||||||
|
Like other tools such as \fBls\fP, ripgrep will alter its output depending on
|
||||||
|
whether stdout is connected to a tty. By default, when printing a tty, ripgrep
|
||||||
|
will enable colors, line numbers and a heading format that lists each matching
|
||||||
|
file path once instead of once per matching line.
|
||||||
|
.sp
|
||||||
|
Tip: to disable all smart filtering and make ripgrep behave a bit more like
|
||||||
|
classical grep, use \fBrg -uuu\fP.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH REGEX SYNTAX
|
||||||
|
ripgrep uses Rust's regex engine by default, which documents its syntax:
|
||||||
|
\fIhttps://docs.rs/regex/1.*/regex/#syntax\fP
|
||||||
|
.sp
|
||||||
|
ripgrep uses byte-oriented regexes, which has some additional documentation:
|
||||||
|
\fIhttps://docs.rs/regex/1.*/regex/bytes/index.html#syntax\fP
|
||||||
|
.sp
|
||||||
|
To a first approximation, ripgrep uses Perl-like regexes without look-around or
|
||||||
|
backreferences. This makes them very similar to the "extended" (ERE) regular
|
||||||
|
expressions supported by *egrep*, but with a few additional features like
|
||||||
|
Unicode character classes.
|
||||||
|
.sp
|
||||||
|
If you're using ripgrep with the \fB\-P/\-\-pcre2\fP flag, then please consult
|
||||||
|
\fIhttps://www.pcre.org\fP or the PCRE2 man pages for documentation on the
|
||||||
|
supported syntax.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH POSITIONAL ARGUMENTS
|
||||||
|
.TP 12
|
||||||
|
\fIPATTERN\fP
|
||||||
|
A regular expression used for searching. To match a pattern beginning with a
|
||||||
|
dash, use the \fB\-e/\-\-regexp\fP option.
|
||||||
|
.TP 12
|
||||||
|
\fIPATH\fP
|
||||||
|
A file or directory to search. Directories are searched recursively. File paths
|
||||||
|
specified explicitly on the command line override glob and ignore rules.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH OPTIONS
|
||||||
|
This section documents all flags that ripgrep accepts. Flags are grouped into
|
||||||
|
categories below according to their function.
|
||||||
|
.sp
|
||||||
|
Note that many options can be turned on and off. In some cases, those flags are
|
||||||
|
not listed explicitly below. For example, the \fB\-\-column\fP flag (listed
|
||||||
|
below) enables column numbers in ripgrep's output, but the \fB\-\-no\-column\fP
|
||||||
|
flag (not listed below) disables them. The reverse can also exist. For example,
|
||||||
|
the \fB\-\-no\-ignore\fP flag (listed below) disables ripgrep's \fBgitignore\fP
|
||||||
|
logic, but the \fB\-\-ignore\fP flag (not listed below) enables it. These
|
||||||
|
flags are useful for overriding a ripgrep configuration file (or alias) on the
|
||||||
|
command line. Each flag's documentation notes whether an inverted flag exists.
|
||||||
|
In all cases, the flag specified last takes precedence.
|
||||||
|
.
|
||||||
|
.SS INPUT OPTIONS
|
||||||
|
!!input!!
|
||||||
|
.
|
||||||
|
.SS SEARCH OPTIONS
|
||||||
|
!!search!!
|
||||||
|
.
|
||||||
|
.SS FILTER OPTIONS
|
||||||
|
!!filter!!
|
||||||
|
.
|
||||||
|
.SS OUTPUT OPTIONS
|
||||||
|
!!output!!
|
||||||
|
.
|
||||||
|
.SS OUTPUT MODES
|
||||||
|
!!output-modes!!
|
||||||
|
.
|
||||||
|
.SS LOGGING OPTIONS
|
||||||
|
!!logging!!
|
||||||
|
.
|
||||||
|
.SS OTHER BEHAVIORS
|
||||||
|
!!other-behaviors!!
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH EXIT STATUS
|
||||||
|
If ripgrep finds a match, then the exit status of the program is \fB0\fP.
|
||||||
|
If no match could be found, then the exit status is \fB1\fP. If an error
|
||||||
|
occurred, then the exit status is always \fB2\fP unless ripgrep was run with
|
||||||
|
the \fB\-q/\-\-quiet\fP flag and a match was found. In summary:
|
||||||
|
.sp
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB0\fP exit status occurs only when at least one match was found, and if
|
||||||
|
no error occurred, unless \fB\-q/\-\-quiet\fP was given.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB1\fP exit status occurs only when no match was found and no error occurred.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB2\fP exit status occurs when an error occurred. This is true for both
|
||||||
|
catastrophic errors (e.g., a regex syntax error) and for soft errors (e.g.,
|
||||||
|
unable to read a file).
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH AUTOMATIC FILTERING
|
||||||
|
ripgrep does a fair bit of automatic filtering by default. This section
|
||||||
|
describes that filtering and how to control it.
|
||||||
|
.sp
|
||||||
|
\fBTIP\fP: To disable automatic filtering, use \fBrg -uuu\fP.
|
||||||
|
.sp
|
||||||
|
ripgrep's automatic "smart" filtering is one of the most apparent
|
||||||
|
differentiating features between ripgrep and other tools like \fBgrep\fP. As
|
||||||
|
such, its behavior may be surprising to users that aren't expecting it.
|
||||||
|
.sp
|
||||||
|
ripgrep does four types of filtering automatically:
|
||||||
|
.sp
|
||||||
|
.
|
||||||
|
.IP 1. 3n
|
||||||
|
Files and directories that match ignore rules are not searched.
|
||||||
|
.IP 2. 3n
|
||||||
|
Hidden files and directories are not searched.
|
||||||
|
.IP 3. 3n
|
||||||
|
Binary files (files with a \fBNUL\fP byte) are not searched.
|
||||||
|
.IP 4. 3n
|
||||||
|
Symbolic links are not followed.
|
||||||
|
.PP
|
||||||
|
The first type of filtering is the most sophisticated. ripgrep will attempt to
|
||||||
|
respect your \fBgitignore\fP rules as faithfully as possible. In particular,
|
||||||
|
this includes the following:
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Any global rules, e.g., in \fB$HOME/.config/git/ignore\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Any rules in relevant \fB.gitignore\fP files. This includes \fB.gitignore\fP
|
||||||
|
files in parent directories that are part of the same \fBgit\fP repository.
|
||||||
|
(Unless \fB\-\-no\-require\-git\fP is given.)
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Any local rules, e.g., in \fB.git/info/exclude\fP.
|
||||||
|
.PP
|
||||||
|
In some cases, ripgrep and \fBgit\fP will not always be in sync in terms
|
||||||
|
of which files are ignored. For example, a file that is ignored via
|
||||||
|
\fB.gitignore\fP but is tracked by \fBgit\fP would not be searched by ripgrep
|
||||||
|
even though \fBgit\fP tracks it. This is unlikely to ever be fixed. Instead,
|
||||||
|
you should either make sure your exclude rules match the files you track
|
||||||
|
precisely, or otherwise use \fBgit grep\fP for search.
|
||||||
|
.sp
|
||||||
|
Additional ignore rules can be provided outside of a \fBgit\fP context:
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Any rules in \fB.ignore\fP. ripgrep will also respect \fB.ignore\fP files in
|
||||||
|
parent directories.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Any rules in \fB.rgignore\fP. ripgrep will also respect \fB.rgignore\fP files
|
||||||
|
in parent directories.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Any rules in files specified with the \fB\-\-ignore\-file\fP flag.
|
||||||
|
.PP
|
||||||
|
The precedence of ignore rules is as follows, with later items overriding
|
||||||
|
earlier items:
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Files given by \fB\-\-ignore\-file\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Global gitignore rules, e.g., from \fB$HOME/.config/git/ignore\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Local rules from \fB.git/info/exclude\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Rules from \fB.gitignore\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Rules from \fB.ignore\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
Rules from \fB.rgignore\fP.
|
||||||
|
.PP
|
||||||
|
So for example, if \fIfoo\fP were in a \fB.gitignore\fP and \fB!\fP\fIfoo\fP
|
||||||
|
were in an \fB.rgignore\fP, then \fIfoo\fP would not be ignored since
|
||||||
|
\fB.rgignore\fP takes precedence over \fB.gitignore\fP.
|
||||||
|
.sp
|
||||||
|
Each of the types of filtering can be configured via command line flags:
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
There are several flags starting with \fB\-\-no\-ignore\fP that toggle which,
|
||||||
|
if any, ignore rules are respected. \fB\-\-no\-ignore\fP by itself will disable
|
||||||
|
all
|
||||||
|
of them.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB\-./\-\-hidden\fP will force ripgrep to search hidden files and directories.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB\-\-binary\fP will force ripgrep to search binary files.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB\-L/\-\-follow\fP will force ripgrep to follow symlinks.
|
||||||
|
.PP
|
||||||
|
As a special short hand, the \fB\-u\fP flag can be specified up to three times.
|
||||||
|
Each additional time incrementally decreases filtering:
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB\-u\fP is equivalent to \fB\-\-no\-ignore\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB\-uu\fP is equivalent to \fB\-\-no\-ignore \-\-hidden\fP.
|
||||||
|
.
|
||||||
|
.IP \(bu 3n
|
||||||
|
\fB\-uuu\fP is equivalent to \fB\-\-no\-ignore \-\-hidden \-\-binary\fP.
|
||||||
|
.PP
|
||||||
|
In particular, \fBrg -uuu\fP should search the same exact content as \fBgrep
|
||||||
|
-r\fP.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH CONFIGURATION FILES
|
||||||
|
ripgrep supports reading configuration files that change ripgrep's default
|
||||||
|
behavior. The format of the configuration file is an "rc" style and is very
|
||||||
|
simple. It is defined by two rules:
|
||||||
|
.
|
||||||
|
.IP 1. 3n
|
||||||
|
Every line is a shell argument, after trimming whitespace.
|
||||||
|
.
|
||||||
|
.IP 2. 3n
|
||||||
|
Lines starting with \fB#\fP (optionally preceded by any amount of whitespace)
|
||||||
|
are ignored.
|
||||||
|
.PP
|
||||||
|
ripgrep will look for a single configuration file if and only if the
|
||||||
|
\fBRIPGREP_CONFIG_PATH\fP environment variable is set and is non-empty.
|
||||||
|
ripgrep will parse arguments from this file on startup and will behave as if
|
||||||
|
the arguments in this file were prepended to any explicit arguments given to
|
||||||
|
ripgrep on the command line. Note though that the \fBrg\fP command you run
|
||||||
|
must still be valid. That is, it must always contain at least one pattern at
|
||||||
|
the command line, even if the configuration file uses the \fB\-e/\-\-regexp\fP
|
||||||
|
flag.
|
||||||
|
.sp
|
||||||
|
For example, if your ripgreprc file contained a single line:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
\-\-smart\-case
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
then the following command
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
would behave identically to the following command:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
rg \-\-smart-case foo
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
Another example is adding types, like so:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
\-\-type-add
|
||||||
|
web:*.{html,css,js}*
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
The above would behave identically to the following command:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
rg \-\-type\-add 'web:*.{html,css,js}*' foo
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
The same applies to using globs. This:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
\-\-glob=!.git
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
or this:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
\-\-glob
|
||||||
|
!.git
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
would behave identically to the following command:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
rg \-\-glob '!.git' foo
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
The bottom line is that every shell argument needs to be on its own line. So
|
||||||
|
for example, a config file containing
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
\-j 4
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
is probably not doing what you intend. Instead, you want
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
\-j
|
||||||
|
4
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
or
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
\-j4
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
ripgrep also provides a flag, \fB\-\-no\-config\fP, that when present will
|
||||||
|
suppress any and all support for configuration. This includes any future
|
||||||
|
support for auto-loading configuration files from pre-determined paths.
|
||||||
|
.sp
|
||||||
|
Conflicts between configuration files and explicit arguments are handled
|
||||||
|
exactly like conflicts in the same command line invocation. That is, assuming
|
||||||
|
your config file contains only \fB\-\-smart\-case\fP, then this command:
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo \-\-case\-sensitive
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
is exactly equivalent to
|
||||||
|
.sp
|
||||||
|
.EX
|
||||||
|
rg \-\-smart\-case foo \-\-case\-sensitive
|
||||||
|
.EE
|
||||||
|
.sp
|
||||||
|
in which case, the \fB\-\-case\-sensitive\fP flag would override the
|
||||||
|
\fB\-\-smart\-case\fP flag.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH SHELL COMPLETION
|
||||||
|
Shell completion files are included in the release tarball for Bash, Fish, Zsh
|
||||||
|
and PowerShell.
|
||||||
|
.sp
|
||||||
|
For \fBbash\fP, move \fBrg.bash\fP to \fB$XDG_CONFIG_HOME/bash_completion\fP or
|
||||||
|
\fB/etc/bash_completion.d/\fP.
|
||||||
|
.sp
|
||||||
|
For \fBfish\fP, move \fBrg.fish\fP to \fB$HOME/.config/fish/completions\fP.
|
||||||
|
.sp
|
||||||
|
For \fBzsh\fP, move \fB_rg\fP to one of your \fB$fpath\fP directories.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH CAVEATS
|
||||||
|
ripgrep may abort unexpectedly when using default settings if it searches a
|
||||||
|
file that is simultaneously truncated. This behavior can be avoided by passing
|
||||||
|
the \fB\-\-no\-mmap\fP flag which will forcefully disable the use of memory
|
||||||
|
maps in all cases.
|
||||||
|
.sp
|
||||||
|
ripgrep may use a large amount of memory depending on a few factors. Firstly,
|
||||||
|
if ripgrep uses parallelism for search (the default), then the entire
|
||||||
|
output for each individual file is buffered into memory in order to prevent
|
||||||
|
interleaving matches in the output. To avoid this, you can disable parallelism
|
||||||
|
with the \fB\-j1\fP flag. Secondly, ripgrep always needs to have at least a
|
||||||
|
single line in memory in order to execute a search. A file with a very long
|
||||||
|
line can thus cause ripgrep to use a lot of memory. Generally, this only occurs
|
||||||
|
when searching binary data with the \fB\-a/\-\-text\fP flag enabled. (When the
|
||||||
|
\fB\-a/\-\-text\fP flag isn't enabled, ripgrep will replace all NUL bytes with
|
||||||
|
line terminators, which typically prevents exorbitant memory usage.) Thirdly,
|
||||||
|
when ripgrep searches a large file using a memory map, the process will likely
|
||||||
|
report its resident memory usage as the size of the file. However, this does
|
||||||
|
not mean ripgrep actually needed to use that much heap memory; the operating
|
||||||
|
system will generally handle this for you.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH VERSION
|
||||||
|
!!VERSION!!
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH HOMEPAGE
|
||||||
|
\fIhttps://github.com/BurntSushi/ripgrep\fP
|
||||||
|
.sp
|
||||||
|
Please report bugs and feature requests to the issue tracker. Please do your
|
||||||
|
best to provide a reproducible test case for bugs. This should include the
|
||||||
|
corpus being searched, the \fBrg\fP command, the actual output and the expected
|
||||||
|
output. Please also include the output of running the same \fBrg\fP command but
|
||||||
|
with the \fB\-\-debug\fP flag.
|
||||||
|
.sp
|
||||||
|
If you have questions that don't obviously fall into the "bug" or "feature
|
||||||
|
request" category, then they are welcome in the Discussions section of the
|
||||||
|
issue tracker: \fIhttps://github.com/BurntSushi/ripgrep/discussions\fP.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.SH AUTHORS
|
||||||
|
Andrew Gallant <\fIjamslam@gmail.com\fP>
|
38
crates/core/flags/doc/template.short.help
Normal file
38
crates/core/flags/doc/template.short.help
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
ripgrep !!VERSION!!
|
||||||
|
Andrew Gallant <jamslam@gmail.com>
|
||||||
|
|
||||||
|
ripgrep (rg) recursively searches the current directory for lines matching
|
||||||
|
a regex pattern. By default, ripgrep will respect gitignore rules and
|
||||||
|
automatically skip hidden files/directories and binary files.
|
||||||
|
|
||||||
|
Use -h for short descriptions and --help for more details.
|
||||||
|
|
||||||
|
Project home page: https://github.com/BurntSushi/ripgrep
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
rg [OPTIONS] PATTERN [PATH ...]
|
||||||
|
|
||||||
|
POSITIONAL ARGUMENTS:
|
||||||
|
<PATTERN> A regular expression used for searching.
|
||||||
|
<PATH>... A file or directory to search.
|
||||||
|
|
||||||
|
INPUT OPTIONS:
|
||||||
|
!!input!!
|
||||||
|
|
||||||
|
SEARCH OPTIONS:
|
||||||
|
!!search!!
|
||||||
|
|
||||||
|
FILTER OPTIONS:
|
||||||
|
!!filter!!
|
||||||
|
|
||||||
|
OUTPUT OPTIONS:
|
||||||
|
!!output!!
|
||||||
|
|
||||||
|
OUTPUT MODES:
|
||||||
|
!!output-modes!!
|
||||||
|
|
||||||
|
LOGGING OPTIONS:
|
||||||
|
!!logging!!
|
||||||
|
|
||||||
|
OTHER BEHAVIORS:
|
||||||
|
!!other-behaviors!!
|
177
crates/core/flags/doc/version.rs
Normal file
177
crates/core/flags/doc/version.rs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*!
|
||||||
|
Provides routines for generating version strings.
|
||||||
|
|
||||||
|
Version strings can be just the digits, an overall short one-line description
|
||||||
|
or something more verbose that includes things like CPU target feature support.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
/// Generates just the numerical part of the version of ripgrep.
|
||||||
|
///
|
||||||
|
/// This includes the git revision hash.
|
||||||
|
pub(crate) fn generate_digits() -> String {
|
||||||
|
let semver = option_env!("CARGO_PKG_VERSION").unwrap_or("N/A");
|
||||||
|
match option_env!("RIPGREP_BUILD_GIT_HASH") {
|
||||||
|
None => semver.to_string(),
|
||||||
|
Some(hash) => format!("{semver} (rev {hash})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a short version string of the form `ripgrep x.y.z`.
|
||||||
|
pub(crate) fn generate_short() -> String {
|
||||||
|
let digits = generate_digits();
|
||||||
|
format!("ripgrep {digits}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a longer multi-line version string.
|
||||||
|
///
|
||||||
|
/// This includes not only the version of ripgrep but some other information
|
||||||
|
/// about its build. For example, SIMD support and PCRE2 support.
|
||||||
|
pub(crate) fn generate_long() -> String {
|
||||||
|
let (compile, runtime) = (compile_cpu_features(), runtime_cpu_features());
|
||||||
|
|
||||||
|
let mut out = String::new();
|
||||||
|
writeln!(out, "{}", generate_short()).unwrap();
|
||||||
|
writeln!(out).unwrap();
|
||||||
|
writeln!(out, "features:{}", features().join(",")).unwrap();
|
||||||
|
if !compile.is_empty() {
|
||||||
|
writeln!(out, "simd(compile):{}", compile.join(",")).unwrap();
|
||||||
|
}
|
||||||
|
if !runtime.is_empty() {
|
||||||
|
writeln!(out, "simd(runtime):{}", runtime.join(",")).unwrap();
|
||||||
|
}
|
||||||
|
let (pcre2_version, _) = generate_pcre2();
|
||||||
|
writeln!(out, "\n{pcre2_version}").unwrap();
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates multi-line version string with PCRE2 information.
|
||||||
|
///
|
||||||
|
/// This also returns whether PCRE2 is actually available in this build of
|
||||||
|
/// ripgrep.
|
||||||
|
pub(crate) fn generate_pcre2() -> (String, bool) {
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
{
|
||||||
|
use grep::pcre2;
|
||||||
|
|
||||||
|
let (major, minor) = pcre2::version();
|
||||||
|
write!(out, "PCRE2 {}.{} is available", major, minor).unwrap();
|
||||||
|
if cfg!(target_pointer_width = "64") && pcre2::is_jit_available() {
|
||||||
|
writeln!(out, " (JIT is available)").unwrap();
|
||||||
|
} else {
|
||||||
|
writeln!(out, " (JIT is unavailable)").unwrap();
|
||||||
|
}
|
||||||
|
(out, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "pcre2"))]
|
||||||
|
{
|
||||||
|
writeln!(out, "PCRE2 is not available in this build of ripgrep.")
|
||||||
|
.unwrap();
|
||||||
|
(out, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the relevant SIMD features supported by the CPU at runtime.
|
||||||
|
///
|
||||||
|
/// This is kind of a dirty violation of abstraction, since it assumes
|
||||||
|
/// knowledge about what specific SIMD features are being used by various
|
||||||
|
/// components.
|
||||||
|
fn runtime_cpu_features() -> Vec<String> {
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
{
|
||||||
|
let mut features = vec![];
|
||||||
|
|
||||||
|
let sse2 = is_x86_feature_detected!("sse2");
|
||||||
|
features.push(format!("{sign}SSE2", sign = sign(sse2)));
|
||||||
|
|
||||||
|
let ssse3 = is_x86_feature_detected!("ssse3");
|
||||||
|
features.push(format!("{sign}SSSE3", sign = sign(ssse3)));
|
||||||
|
|
||||||
|
let avx2 = is_x86_feature_detected!("avx2");
|
||||||
|
features.push(format!("{sign}AVX2", sign = sign(avx2)));
|
||||||
|
|
||||||
|
features
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
{
|
||||||
|
let mut features = vec![];
|
||||||
|
|
||||||
|
// memchr and aho-corasick only use NEON when it is available at
|
||||||
|
// compile time. This isn't strictly necessary, but NEON is supposed
|
||||||
|
// to be available for all aarch64 targets. If this isn't true, please
|
||||||
|
// file an issue at https://github.com/BurntSushi/memchr.
|
||||||
|
let neon = cfg!(target_feature = "neon");
|
||||||
|
features.push(format!("{sign}NEON", sign = sign(neon)));
|
||||||
|
|
||||||
|
features
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||||
|
{
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the SIMD features supported while compiling ripgrep.
|
||||||
|
///
|
||||||
|
/// In essence, any features listed here are required to run ripgrep correctly.
|
||||||
|
///
|
||||||
|
/// This is kind of a dirty violation of abstraction, since it assumes
|
||||||
|
/// knowledge about what specific SIMD features are being used by various
|
||||||
|
/// components.
|
||||||
|
///
|
||||||
|
/// An easy way to enable everything available on your current CPU is to
|
||||||
|
/// compile ripgrep with `RUSTFLAGS="-C target-cpu=native"`. But note that
|
||||||
|
/// the binary produced by this will not be portable.
|
||||||
|
fn compile_cpu_features() -> Vec<String> {
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
{
|
||||||
|
let mut features = vec![];
|
||||||
|
|
||||||
|
let sse2 = cfg!(target_feature = "sse2");
|
||||||
|
features.push(format!("{sign}SSE2", sign = sign(sse2)));
|
||||||
|
|
||||||
|
let ssse3 = cfg!(target_feature = "ssse3");
|
||||||
|
features.push(format!("{sign}SSSE3", sign = sign(ssse3)));
|
||||||
|
|
||||||
|
let avx2 = cfg!(target_feature = "avx2");
|
||||||
|
features.push(format!("{sign}AVX2", sign = sign(avx2)));
|
||||||
|
|
||||||
|
features
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
{
|
||||||
|
let mut features = vec![];
|
||||||
|
|
||||||
|
let neon = cfg!(target_feature = "neon");
|
||||||
|
features.push(format!("{sign}NEON", sign = sign(neon)));
|
||||||
|
|
||||||
|
features
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||||
|
{
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of "features" supported (or not) by this build of ripgrpe.
|
||||||
|
fn features() -> Vec<String> {
|
||||||
|
let mut features = vec![];
|
||||||
|
|
||||||
|
let pcre2 = cfg!(feature = "pcre2");
|
||||||
|
features.push(format!("{sign}pcre2", sign = sign(pcre2)));
|
||||||
|
|
||||||
|
features
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `+` when `enabled` is `true` and `-` otherwise.
|
||||||
|
fn sign(enabled: bool) -> &'static str {
|
||||||
|
if enabled {
|
||||||
|
"+"
|
||||||
|
} else {
|
||||||
|
"-"
|
||||||
|
}
|
||||||
|
}
|
1471
crates/core/flags/hiargs.rs
Normal file
1471
crates/core/flags/hiargs.rs
Normal file
File diff suppressed because it is too large
Load Diff
758
crates/core/flags/lowargs.rs
Normal file
758
crates/core/flags/lowargs.rs
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
/*!
|
||||||
|
Provides the definition of low level arguments from CLI flags.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use {
|
||||||
|
bstr::{BString, ByteVec},
|
||||||
|
grep::printer::{HyperlinkFormat, UserColorSpec},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A collection of "low level" arguments.
|
||||||
|
///
|
||||||
|
/// The "low level" here is meant to constrain this type to be as close to the
|
||||||
|
/// actual CLI flags and arguments as possible. Namely, other than some
|
||||||
|
/// convenience types to help validate flag values and deal with overrides
|
||||||
|
/// between flags, these low level arguments do not contain any higher level
|
||||||
|
/// abstractions.
|
||||||
|
///
|
||||||
|
/// Another self-imposed constraint is that populating low level arguments
|
||||||
|
/// should not require anything other than validating what the user has
|
||||||
|
/// provided. For example, low level arguments should not contain a
|
||||||
|
/// `HyperlinkConfig`, since in order to get a full configuration, one needs to
|
||||||
|
/// discover the hostname of the current system (which might require running a
|
||||||
|
/// binary or a syscall).
|
||||||
|
///
|
||||||
|
/// Low level arguments are populated by the parser directly via the `update`
|
||||||
|
/// method on the corresponding implementation of the `Flag` trait.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct LowArgs {
|
||||||
|
// Essential arguments.
|
||||||
|
pub(crate) special: Option<SpecialMode>,
|
||||||
|
pub(crate) mode: Mode,
|
||||||
|
pub(crate) positional: Vec<OsString>,
|
||||||
|
pub(crate) patterns: Vec<PatternSource>,
|
||||||
|
// Everything else, sorted lexicographically.
|
||||||
|
pub(crate) binary: BinaryMode,
|
||||||
|
pub(crate) boundary: Option<BoundaryMode>,
|
||||||
|
pub(crate) buffer: BufferMode,
|
||||||
|
pub(crate) byte_offset: bool,
|
||||||
|
pub(crate) case: CaseMode,
|
||||||
|
pub(crate) color: ColorChoice,
|
||||||
|
pub(crate) colors: Vec<UserColorSpec>,
|
||||||
|
pub(crate) column: Option<bool>,
|
||||||
|
pub(crate) context: ContextMode,
|
||||||
|
pub(crate) context_separator: ContextSeparator,
|
||||||
|
pub(crate) crlf: bool,
|
||||||
|
pub(crate) dfa_size_limit: Option<usize>,
|
||||||
|
pub(crate) encoding: EncodingMode,
|
||||||
|
pub(crate) engine: EngineChoice,
|
||||||
|
pub(crate) field_context_separator: FieldContextSeparator,
|
||||||
|
pub(crate) field_match_separator: FieldMatchSeparator,
|
||||||
|
pub(crate) fixed_strings: bool,
|
||||||
|
pub(crate) follow: bool,
|
||||||
|
pub(crate) glob_case_insensitive: bool,
|
||||||
|
pub(crate) globs: Vec<String>,
|
||||||
|
pub(crate) heading: Option<bool>,
|
||||||
|
pub(crate) hidden: bool,
|
||||||
|
pub(crate) hostname_bin: Option<PathBuf>,
|
||||||
|
pub(crate) hyperlink_format: HyperlinkFormat,
|
||||||
|
pub(crate) iglobs: Vec<String>,
|
||||||
|
pub(crate) ignore_file: Vec<PathBuf>,
|
||||||
|
pub(crate) ignore_file_case_insensitive: bool,
|
||||||
|
pub(crate) include_zero: bool,
|
||||||
|
pub(crate) invert_match: bool,
|
||||||
|
pub(crate) line_number: Option<bool>,
|
||||||
|
pub(crate) logging: Option<LoggingMode>,
|
||||||
|
pub(crate) max_columns: Option<u64>,
|
||||||
|
pub(crate) max_columns_preview: bool,
|
||||||
|
pub(crate) max_count: Option<u64>,
|
||||||
|
pub(crate) max_depth: Option<usize>,
|
||||||
|
pub(crate) max_filesize: Option<u64>,
|
||||||
|
pub(crate) mmap: MmapMode,
|
||||||
|
pub(crate) multiline: bool,
|
||||||
|
pub(crate) multiline_dotall: bool,
|
||||||
|
pub(crate) no_config: bool,
|
||||||
|
pub(crate) no_ignore_dot: bool,
|
||||||
|
pub(crate) no_ignore_exclude: bool,
|
||||||
|
pub(crate) no_ignore_files: bool,
|
||||||
|
pub(crate) no_ignore_global: bool,
|
||||||
|
pub(crate) no_ignore_messages: bool,
|
||||||
|
pub(crate) no_ignore_parent: bool,
|
||||||
|
pub(crate) no_ignore_vcs: bool,
|
||||||
|
pub(crate) no_messages: bool,
|
||||||
|
pub(crate) no_require_git: bool,
|
||||||
|
pub(crate) no_unicode: bool,
|
||||||
|
pub(crate) null: bool,
|
||||||
|
pub(crate) null_data: bool,
|
||||||
|
pub(crate) one_file_system: bool,
|
||||||
|
pub(crate) only_matching: bool,
|
||||||
|
pub(crate) path_separator: Option<u8>,
|
||||||
|
pub(crate) pre: Option<PathBuf>,
|
||||||
|
pub(crate) pre_glob: Vec<String>,
|
||||||
|
pub(crate) quiet: bool,
|
||||||
|
pub(crate) regex_size_limit: Option<usize>,
|
||||||
|
pub(crate) replace: Option<BString>,
|
||||||
|
pub(crate) search_zip: bool,
|
||||||
|
pub(crate) sort: Option<SortMode>,
|
||||||
|
pub(crate) stats: bool,
|
||||||
|
pub(crate) stop_on_nonmatch: bool,
|
||||||
|
pub(crate) threads: Option<usize>,
|
||||||
|
pub(crate) trim: bool,
|
||||||
|
pub(crate) type_changes: Vec<TypeChange>,
|
||||||
|
pub(crate) unrestricted: usize,
|
||||||
|
pub(crate) vimgrep: bool,
|
||||||
|
pub(crate) with_filename: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A "special" mode that supercedes everything else.
|
||||||
|
///
|
||||||
|
/// When one of these modes is present, it overrides everything else and causes
|
||||||
|
/// ripgrep to short-circuit. In particular, we avoid converting low-level
|
||||||
|
/// argument types into higher level arguments types that can fail for various
|
||||||
|
/// reasons related to the environment. (Parsing the low-level arguments can
|
||||||
|
/// fail too, but usually not in a way that can't be worked around by removing
|
||||||
|
/// the corresponding arguments from the CLI command.) This is overall a hedge
|
||||||
|
/// to ensure that version and help information are basically always available.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum SpecialMode {
|
||||||
|
/// Show a condensed version of "help" output. Generally speaking, this
|
||||||
|
/// shows each flag and an extremely terse description of that flag on
|
||||||
|
/// a single line. This corresponds to the `-h` flag.
|
||||||
|
HelpShort,
|
||||||
|
/// Shows a very verbose version of the "help" output. The docs for some
|
||||||
|
/// flags will be paragraphs long. This corresponds to the `--help` flag.
|
||||||
|
HelpLong,
|
||||||
|
/// Show condensed version information. e.g., `ripgrep x.y.z`.
|
||||||
|
VersionShort,
|
||||||
|
/// Show verbose version information. Includes "short" information as well
|
||||||
|
/// as features included in the build.
|
||||||
|
VersionLong,
|
||||||
|
/// Show PCRE2's version information, or an error if this version of
|
||||||
|
/// ripgrep wasn't compiled with PCRE2 support.
|
||||||
|
VersionPCRE2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The overall mode that ripgrep should operate in.
|
||||||
|
///
|
||||||
|
/// If ripgrep were designed without the legacy of grep, these would probably
|
||||||
|
/// be sub-commands? Perhaps not, since they aren't as frequently used.
|
||||||
|
///
|
||||||
|
/// The point of putting these in one enum is that they are all mutually
|
||||||
|
/// exclusive and override one another.
|
||||||
|
///
|
||||||
|
/// Note that -h/--help and -V/--version are not included in this because
|
||||||
|
/// they always overrides everything else, regardless of where it appears
|
||||||
|
/// in the command line. They are treated as "special" modes that short-circuit
|
||||||
|
/// ripgrep's usual flow.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum Mode {
|
||||||
|
/// ripgrep will execute a search of some kind.
|
||||||
|
Search(SearchMode),
|
||||||
|
/// Show the files that *would* be searched, but don't actually search
|
||||||
|
/// them.
|
||||||
|
Files,
|
||||||
|
/// List all file type definitions configured, including the default file
|
||||||
|
/// types and any additional file types added to the command line.
|
||||||
|
Types,
|
||||||
|
/// Generate various things like the man page and completion files.
|
||||||
|
Generate(GenerateMode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Mode {
|
||||||
|
fn default() -> Mode {
|
||||||
|
Mode::Search(SearchMode::Standard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
/// Update this mode to the new mode while implementing various override
|
||||||
|
/// semantics. For example, a search mode cannot override a non-search
|
||||||
|
/// mode.
|
||||||
|
pub(crate) fn update(&mut self, new: Mode) {
|
||||||
|
match *self {
|
||||||
|
// If we're in a search mode, then anything can override it.
|
||||||
|
Mode::Search(_) => *self = new,
|
||||||
|
_ => {
|
||||||
|
// Once we're in a non-search mode, other non-search modes
|
||||||
|
// can override it. But search modes cannot. So for example,
|
||||||
|
// `--files -l` will still be Mode::Files.
|
||||||
|
if !matches!(*self, Mode::Search(_)) {
|
||||||
|
*self = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of search that ripgrep is going to perform.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum SearchMode {
|
||||||
|
/// The default standard mode of operation. ripgrep looks for matches and
|
||||||
|
/// prints them when found.
|
||||||
|
///
|
||||||
|
/// There is no specific flag for this mode since it's the default. But
|
||||||
|
/// some of the modes below, like JSON, have negation flags like --no-json
|
||||||
|
/// that let you revert back to this default mode.
|
||||||
|
Standard,
|
||||||
|
/// Show files containing at least one match.
|
||||||
|
FilesWithMatches,
|
||||||
|
/// Show files that don't contain any matches.
|
||||||
|
FilesWithoutMatch,
|
||||||
|
/// Show files containing at least one match and the number of matching
|
||||||
|
/// lines.
|
||||||
|
Count,
|
||||||
|
/// Show files containing at least one match and the total number of
|
||||||
|
/// matches.
|
||||||
|
CountMatches,
|
||||||
|
/// Print matches in a JSON lines format.
|
||||||
|
JSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The thing to generate via the --generate flag.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum GenerateMode {
|
||||||
|
/// Generate the raw roff used for the man page.
|
||||||
|
Man,
|
||||||
|
/// Completions for bash.
|
||||||
|
CompleteBash,
|
||||||
|
/// Completions for zsh.
|
||||||
|
CompleteZsh,
|
||||||
|
/// Completions for fish.
|
||||||
|
CompleteFish,
|
||||||
|
/// Completions for PowerShell.
|
||||||
|
CompletePowerShell,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates how ripgrep should treat binary data.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum BinaryMode {
|
||||||
|
/// Automatically determine the binary mode to use. Essentially, when
|
||||||
|
/// a file is searched explicitly, then it will be searched using the
|
||||||
|
/// `SearchAndSuppress` strategy. Otherwise, it will be searched in a way
|
||||||
|
/// that attempts to skip binary files as much as possible. That is, once
|
||||||
|
/// a file is classified as binary, searching will immediately stop.
|
||||||
|
Auto,
|
||||||
|
/// Search files even when they have binary data, but if a match is found,
|
||||||
|
/// suppress it and emit a warning.
|
||||||
|
///
|
||||||
|
/// In this mode, `NUL` bytes are replaced with line terminators. This is
|
||||||
|
/// a heuristic meant to reduce heap memory usage, since true binary data
|
||||||
|
/// isn't line oriented. If one attempts to treat such data as line
|
||||||
|
/// oriented, then one may wind up with impractically large lines. For
|
||||||
|
/// example, many binary files contain very long runs of NUL bytes.
|
||||||
|
SearchAndSuppress,
|
||||||
|
/// Treat all files as if they were plain text. There's no skipping and no
|
||||||
|
/// replacement of `NUL` bytes with line terminators.
|
||||||
|
AsText,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BinaryMode {
|
||||||
|
fn default() -> BinaryMode {
|
||||||
|
BinaryMode::Auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates what kind of boundary mode to use (line or word).
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum BoundaryMode {
|
||||||
|
/// Only allow matches when surrounded by line bounaries.
|
||||||
|
Line,
|
||||||
|
/// Only allow matches when surrounded by word bounaries.
|
||||||
|
Word,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates the buffer mode that ripgrep should use when printing output.
|
||||||
|
///
|
||||||
|
/// The default is `Auto`.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum BufferMode {
|
||||||
|
/// Select the buffer mode, 'line' or 'block', automatically based on
|
||||||
|
/// whether stdout is connected to a tty.
|
||||||
|
Auto,
|
||||||
|
/// Flush the output buffer whenever a line terminator is seen.
|
||||||
|
///
|
||||||
|
/// This is useful when wants to see search results more immediately,
|
||||||
|
/// for example, with `tail -f`.
|
||||||
|
Line,
|
||||||
|
/// Flush the output buffer whenever it reaches some fixed size. The size
|
||||||
|
/// is usually big enough to hold many lines.
|
||||||
|
///
|
||||||
|
/// This is useful for maximum performance, particularly when printing
|
||||||
|
/// lots of results.
|
||||||
|
Block,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BufferMode {
|
||||||
|
fn default() -> BufferMode {
|
||||||
|
BufferMode::Auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates the case mode for how to interpret all patterns given to ripgrep.
|
||||||
|
///
|
||||||
|
/// The default is `Sensitive`.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum CaseMode {
|
||||||
|
/// Patterns are matched case sensitively. i.e., `a` does not match `A`.
|
||||||
|
Sensitive,
|
||||||
|
/// Patterns are matched case insensitively. i.e., `a` does match `A`.
|
||||||
|
Insensitive,
|
||||||
|
/// Patterns are automatically matched case insensitively only when they
|
||||||
|
/// consist of all lowercase literal characters. For example, the pattern
|
||||||
|
/// `a` will match `A` but `A` will not match `a`.
|
||||||
|
Smart,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CaseMode {
|
||||||
|
fn default() -> CaseMode {
|
||||||
|
CaseMode::Sensitive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates whether ripgrep should include color/hyperlinks in its output.
|
||||||
|
///
|
||||||
|
/// The default is `Auto`.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum ColorChoice {
|
||||||
|
/// Color and hyperlinks will never be used.
|
||||||
|
Never,
|
||||||
|
/// Color and hyperlinks will be used only when stdout is connected to a
|
||||||
|
/// tty.
|
||||||
|
Auto,
|
||||||
|
/// Color will always be used.
|
||||||
|
Always,
|
||||||
|
/// Color will always be used and only ANSI escapes will be used.
|
||||||
|
///
|
||||||
|
/// This only makes sense in the context of legacy Windows console APIs.
|
||||||
|
/// At time of writing, ripgrep will try to use the legacy console APIs
|
||||||
|
/// if ANSI coloring isn't believed to be possible. This option will force
|
||||||
|
/// ripgrep to use ANSI coloring.
|
||||||
|
Ansi,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ColorChoice {
|
||||||
|
fn default() -> ColorChoice {
|
||||||
|
ColorChoice::Auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorChoice {
|
||||||
|
/// Convert this color choice to the corresponding termcolor type.
|
||||||
|
pub(crate) fn to_termcolor(&self) -> termcolor::ColorChoice {
|
||||||
|
match *self {
|
||||||
|
ColorChoice::Never => termcolor::ColorChoice::Never,
|
||||||
|
ColorChoice::Auto => termcolor::ColorChoice::Auto,
|
||||||
|
ColorChoice::Always => termcolor::ColorChoice::Always,
|
||||||
|
ColorChoice::Ansi => termcolor::ColorChoice::AlwaysAnsi,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates the line context options ripgrep should use for output.
|
||||||
|
///
|
||||||
|
/// The default is no context at all.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum ContextMode {
|
||||||
|
/// All lines will be printed. That is, the context is unbounded.
|
||||||
|
Passthru,
|
||||||
|
/// Only show a certain number of lines before and after each match.
|
||||||
|
Limited(ContextModeLimited),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ContextMode {
|
||||||
|
fn default() -> ContextMode {
|
||||||
|
ContextMode::Limited(ContextModeLimited::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextMode {
|
||||||
|
/// Set the "before" context.
|
||||||
|
///
|
||||||
|
/// If this was set to "passthru" context, then it is overridden in favor
|
||||||
|
/// of limited context with the given value for "before" and `0` for
|
||||||
|
/// "after."
|
||||||
|
pub(crate) fn set_before(&mut self, lines: usize) {
|
||||||
|
match *self {
|
||||||
|
ContextMode::Passthru => {
|
||||||
|
*self = ContextMode::Limited(ContextModeLimited {
|
||||||
|
before: Some(lines),
|
||||||
|
after: None,
|
||||||
|
both: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ContextMode::Limited(ContextModeLimited {
|
||||||
|
ref mut before,
|
||||||
|
..
|
||||||
|
}) => *before = Some(lines),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the "after" context.
|
||||||
|
///
|
||||||
|
/// If this was set to "passthru" context, then it is overridden in favor
|
||||||
|
/// of limited context with the given value for "after" and `0` for
|
||||||
|
/// "before."
|
||||||
|
pub(crate) fn set_after(&mut self, lines: usize) {
|
||||||
|
match *self {
|
||||||
|
ContextMode::Passthru => {
|
||||||
|
*self = ContextMode::Limited(ContextModeLimited {
|
||||||
|
before: None,
|
||||||
|
after: Some(lines),
|
||||||
|
both: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ContextMode::Limited(ContextModeLimited {
|
||||||
|
ref mut after, ..
|
||||||
|
}) => *after = Some(lines),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the "both" context.
|
||||||
|
///
|
||||||
|
/// If this was set to "passthru" context, then it is overridden in favor
|
||||||
|
/// of limited context with the given value for "both" and `None` for
|
||||||
|
/// "before" and "after".
|
||||||
|
pub(crate) fn set_both(&mut self, lines: usize) {
|
||||||
|
match *self {
|
||||||
|
ContextMode::Passthru => {
|
||||||
|
*self = ContextMode::Limited(ContextModeLimited {
|
||||||
|
before: None,
|
||||||
|
after: None,
|
||||||
|
both: Some(lines),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ContextMode::Limited(ContextModeLimited {
|
||||||
|
ref mut both, ..
|
||||||
|
}) => *both = Some(lines),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A convenience function for use in tests that returns the limited
|
||||||
|
/// context. If this mode isn't limited, then it panics.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn get_limited(&self) -> (usize, usize) {
|
||||||
|
match *self {
|
||||||
|
ContextMode::Passthru => unreachable!("context mode is passthru"),
|
||||||
|
ContextMode::Limited(ref limited) => limited.get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A context mode for a finite number of lines.
|
||||||
|
///
|
||||||
|
/// Namely, this indicates that a specific number of lines (possibly zero)
|
||||||
|
/// should be shown before and/or after each matching line.
|
||||||
|
///
|
||||||
|
/// Note that there is a subtle difference between `Some(0)` and `None`. In the
|
||||||
|
/// former case, it happens when `0` is given explicitly, where as `None` is
|
||||||
|
/// the default value and occurs when no value is specified.
|
||||||
|
///
|
||||||
|
/// `both` is only set by the -C/--context flag. The reason why we don't just
|
||||||
|
/// set before = after = --context is because the before and after context
|
||||||
|
/// settings always take precedent over the -C/--context setting, regardless of
|
||||||
|
/// order. Thus, we need to keep track of them separately.
|
||||||
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
|
pub(crate) struct ContextModeLimited {
|
||||||
|
before: Option<usize>,
|
||||||
|
after: Option<usize>,
|
||||||
|
both: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextModeLimited {
|
||||||
|
/// Returns the specific number of contextual lines that should be shown
|
||||||
|
/// around each match. This takes proper precedent into account, i.e.,
|
||||||
|
/// that `before` and `after` both partially override `both` in all cases.
|
||||||
|
///
|
||||||
|
/// By default, this returns `(0, 0)`.
|
||||||
|
pub(crate) fn get(&self) -> (usize, usize) {
|
||||||
|
let (mut before, mut after) =
|
||||||
|
self.both.map(|lines| (lines, lines)).unwrap_or((0, 0));
|
||||||
|
// --before and --after always override --context, regardless
|
||||||
|
// of where they appear relative to each other.
|
||||||
|
if let Some(lines) = self.before {
|
||||||
|
before = lines;
|
||||||
|
}
|
||||||
|
if let Some(lines) = self.after {
|
||||||
|
after = lines;
|
||||||
|
}
|
||||||
|
(before, after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the separator to use between non-contiguous sections of
|
||||||
|
/// contextual lines.
|
||||||
|
///
|
||||||
|
/// The default is `--`.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct ContextSeparator(Option<BString>);
|
||||||
|
|
||||||
|
impl Default for ContextSeparator {
|
||||||
|
fn default() -> ContextSeparator {
|
||||||
|
ContextSeparator(Some(BString::from("--")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextSeparator {
|
||||||
|
/// Create a new context separator from the user provided argument. This
|
||||||
|
/// handles unescaping.
|
||||||
|
pub(crate) fn new(os: &OsStr) -> anyhow::Result<ContextSeparator> {
|
||||||
|
let Some(string) = os.to_str() else {
|
||||||
|
anyhow::bail!(
|
||||||
|
"separator must be valid UTF-8 (use escape sequences \
|
||||||
|
to provide a separator that is not valid UTF-8)"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Ok(ContextSeparator(Some(Vec::unescape_bytes(string).into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new separator that intructs the printer to disable contextual
|
||||||
|
/// separators entirely.
|
||||||
|
pub(crate) fn disabled() -> ContextSeparator {
|
||||||
|
ContextSeparator(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the raw bytes of this separator.
|
||||||
|
///
|
||||||
|
/// If context separators were disabled, then this returns `None`.
|
||||||
|
///
|
||||||
|
/// Note that this may return a `Some` variant with zero bytes.
|
||||||
|
pub(crate) fn into_bytes(self) -> Option<Vec<u8>> {
|
||||||
|
self.0.map(|sep| sep.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The encoding mode the searcher will use.
|
||||||
|
///
|
||||||
|
/// The default is `Auto`.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum EncodingMode {
|
||||||
|
/// Use only BOM sniffing to auto-detect an encoding.
|
||||||
|
Auto,
|
||||||
|
/// Use an explicit encoding forcefully, but let BOM sniffing override it.
|
||||||
|
Some(grep::searcher::Encoding),
|
||||||
|
/// Use no explicit encoding and disable all BOM sniffing. This will
|
||||||
|
/// always result in searching the raw bytes, regardless of their
|
||||||
|
/// true encoding.
|
||||||
|
Disabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EncodingMode {
|
||||||
|
fn default() -> EncodingMode {
|
||||||
|
EncodingMode::Auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The regex engine to use.
|
||||||
|
///
|
||||||
|
/// The default is `Default`.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum EngineChoice {
|
||||||
|
/// Uses the default regex engine: Rust's `regex` crate.
|
||||||
|
///
|
||||||
|
/// (Well, technically it uses `regex-automata`, but `regex-automata` is
|
||||||
|
/// the implementation of the `regex` crate.)
|
||||||
|
Default,
|
||||||
|
/// Dynamically select the right engine to use.
|
||||||
|
///
|
||||||
|
/// This works by trying to use the default engine, and if the pattern does
|
||||||
|
/// not compile, it switches over to the PCRE2 engine if it's available.
|
||||||
|
Auto,
|
||||||
|
/// Uses the PCRE2 regex engine if it's available.
|
||||||
|
PCRE2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EngineChoice {
|
||||||
|
fn default() -> EngineChoice {
|
||||||
|
EngineChoice::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The field context separator to use to between metadata for each contextual
|
||||||
|
/// line.
|
||||||
|
///
|
||||||
|
/// The default is `-`.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct FieldContextSeparator(BString);
|
||||||
|
|
||||||
|
impl Default for FieldContextSeparator {
|
||||||
|
fn default() -> FieldContextSeparator {
|
||||||
|
FieldContextSeparator(BString::from("-"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldContextSeparator {
|
||||||
|
/// Create a new separator from the given argument value provided by the
|
||||||
|
/// user. Unescaping it automatically handled.
|
||||||
|
pub(crate) fn new(os: &OsStr) -> anyhow::Result<FieldContextSeparator> {
|
||||||
|
let Some(string) = os.to_str() else {
|
||||||
|
anyhow::bail!(
|
||||||
|
"separator must be valid UTF-8 (use escape sequences \
|
||||||
|
to provide a separator that is not valid UTF-8)"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Ok(FieldContextSeparator(Vec::unescape_bytes(string).into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the raw bytes of this separator.
|
||||||
|
///
|
||||||
|
/// Note that this may return an empty `Vec`.
|
||||||
|
pub(crate) fn into_bytes(self) -> Vec<u8> {
|
||||||
|
self.0.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The field match separator to use to between metadata for each matching
|
||||||
|
/// line.
|
||||||
|
///
|
||||||
|
/// The default is `:`.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct FieldMatchSeparator(BString);
|
||||||
|
|
||||||
|
impl Default for FieldMatchSeparator {
|
||||||
|
fn default() -> FieldMatchSeparator {
|
||||||
|
FieldMatchSeparator(BString::from(":"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldMatchSeparator {
|
||||||
|
/// Create a new separator from the given argument value provided by the
|
||||||
|
/// user. Unescaping it automatically handled.
|
||||||
|
pub(crate) fn new(os: &OsStr) -> anyhow::Result<FieldMatchSeparator> {
|
||||||
|
let Some(string) = os.to_str() else {
|
||||||
|
anyhow::bail!(
|
||||||
|
"separator must be valid UTF-8 (use escape sequences \
|
||||||
|
to provide a separator that is not valid UTF-8)"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Ok(FieldMatchSeparator(Vec::unescape_bytes(string).into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the raw bytes of this separator.
|
||||||
|
///
|
||||||
|
/// Note that this may return an empty `Vec`.
|
||||||
|
pub(crate) fn into_bytes(self) -> Vec<u8> {
|
||||||
|
self.0.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of logging to do. `Debug` emits some details while `Trace` emits
|
||||||
|
/// much more.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum LoggingMode {
|
||||||
|
Debug,
|
||||||
|
Trace,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates when to use memory maps.
|
||||||
|
///
|
||||||
|
/// The default is `Auto`.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum MmapMode {
|
||||||
|
/// This instructs ripgrep to use heuristics for selecting when to and not
|
||||||
|
/// to use memory maps for searching.
|
||||||
|
Auto,
|
||||||
|
/// This instructs ripgrep to always try memory maps when possible. (Memory
|
||||||
|
/// maps are not possible to use in all circumstances, for example, for
|
||||||
|
/// virtual files.)
|
||||||
|
AlwaysTryMmap,
|
||||||
|
/// Never use memory maps under any circumstances. This includes even
|
||||||
|
/// when multi-line search is enabled where ripgrep will read the entire
|
||||||
|
/// contents of a file on to the heap before searching it.
|
||||||
|
Never,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MmapMode {
|
||||||
|
fn default() -> MmapMode {
|
||||||
|
MmapMode::Auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a source of patterns that ripgrep should search for.
|
||||||
|
///
|
||||||
|
/// The reason to unify these is so that we can retain the order of `-f/--flag`
|
||||||
|
/// and `-e/--regexp` flags relative to one another.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum PatternSource {
|
||||||
|
/// Comes from the `-e/--regexp` flag.
|
||||||
|
Regexp(String),
|
||||||
|
/// Comes from the `-f/--file` flag.
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sort criteria, if present.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct SortMode {
|
||||||
|
/// Whether to reverse the sort criteria (i.e., descending order).
|
||||||
|
pub(crate) reverse: bool,
|
||||||
|
/// The actual sorting criteria.
|
||||||
|
pub(crate) kind: SortModeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The criteria to use for sorting.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum SortModeKind {
|
||||||
|
/// Sort by path.
|
||||||
|
Path,
|
||||||
|
/// Sort by last modified time.
|
||||||
|
LastModified,
|
||||||
|
/// Sort by last accessed time.
|
||||||
|
LastAccessed,
|
||||||
|
/// Sort by creation time.
|
||||||
|
Created,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SortMode {
|
||||||
|
/// Checks whether the selected sort mode is supported. If it isn't, an
|
||||||
|
/// error (hopefully explaining why) is returned.
|
||||||
|
pub(crate) fn supported(&self) -> anyhow::Result<()> {
|
||||||
|
match self.kind {
|
||||||
|
SortModeKind::Path => Ok(()),
|
||||||
|
SortModeKind::LastModified => {
|
||||||
|
let md = std::env::current_exe()
|
||||||
|
.and_then(|p| p.metadata())
|
||||||
|
.and_then(|md| md.modified());
|
||||||
|
let Err(err) = md else { return Ok(()) };
|
||||||
|
anyhow::bail!(
|
||||||
|
"sorting by last modified isn't supported: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SortModeKind::LastAccessed => {
|
||||||
|
let md = std::env::current_exe()
|
||||||
|
.and_then(|p| p.metadata())
|
||||||
|
.and_then(|md| md.accessed());
|
||||||
|
let Err(err) = md else { return Ok(()) };
|
||||||
|
anyhow::bail!(
|
||||||
|
"sorting by last accessed isn't supported: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SortModeKind::Created => {
|
||||||
|
let md = std::env::current_exe()
|
||||||
|
.and_then(|p| p.metadata())
|
||||||
|
.and_then(|md| md.created());
|
||||||
|
let Err(err) = md else { return Ok(()) };
|
||||||
|
anyhow::bail!(
|
||||||
|
"sorting by creation time isn't supported: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single instance of either a change or a selection of one ripgrep's
|
||||||
|
/// file types.
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum TypeChange {
|
||||||
|
/// Clear the given type from ripgrep.
|
||||||
|
Clear { name: String },
|
||||||
|
/// Add the given type definition (name and glob) to ripgrep.
|
||||||
|
Add { def: String },
|
||||||
|
/// Select the given type for filtering.
|
||||||
|
Select { name: String },
|
||||||
|
/// Select the given type for filtering but negate it.
|
||||||
|
Negate { name: String },
|
||||||
|
}
|
302
crates/core/flags/mod.rs
Normal file
302
crates/core/flags/mod.rs
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
/*!
|
||||||
|
Defines ripgrep's command line interface.
|
||||||
|
|
||||||
|
This modules deals with everything involving ripgrep's flags and positional
|
||||||
|
arguments. This includes generating shell completions, `--help` output and even
|
||||||
|
ripgrep's man page. It's also responsible for parsing and validating every
|
||||||
|
flag (including reading ripgrep's config file), and manages the contact points
|
||||||
|
between these flags and ripgrep's cast of supporting libraries. For example,
|
||||||
|
once [`HiArgs`] has been created, it knows how to create a multi threaded
|
||||||
|
recursive directory traverser.
|
||||||
|
*/
|
||||||
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
fmt::Debug,
|
||||||
|
panic::{RefUnwindSafe, UnwindSafe},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) use crate::flags::{
|
||||||
|
complete::{
|
||||||
|
bash::generate as generate_complete_bash,
|
||||||
|
fish::generate as generate_complete_fish,
|
||||||
|
powershell::generate as generate_complete_powershell,
|
||||||
|
zsh::generate as generate_complete_zsh,
|
||||||
|
},
|
||||||
|
doc::{
|
||||||
|
help::{
|
||||||
|
generate_long as generate_help_long,
|
||||||
|
generate_short as generate_help_short,
|
||||||
|
},
|
||||||
|
man::generate as generate_man_page,
|
||||||
|
version::{
|
||||||
|
generate_long as generate_version_long,
|
||||||
|
generate_pcre2 as generate_version_pcre2,
|
||||||
|
generate_short as generate_version_short,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hiargs::HiArgs,
|
||||||
|
lowargs::{GenerateMode, Mode, SearchMode, SpecialMode},
|
||||||
|
parse::{parse, ParseResult},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod complete;
|
||||||
|
mod config;
|
||||||
|
mod defs;
|
||||||
|
mod doc;
|
||||||
|
mod hiargs;
|
||||||
|
mod lowargs;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
/// A trait that encapsulates the definition of an optional flag for ripgrep.
|
||||||
|
///
|
||||||
|
/// This trait is meant to be used via dynamic dispatch. Namely, the `defs`
|
||||||
|
/// module provides a single global slice of `&dyn Flag` values correspondings
|
||||||
|
/// to all of the flags in ripgrep.
|
||||||
|
///
|
||||||
|
/// ripgrep's required positional arguments are handled by the parser and by
|
||||||
|
/// the conversion from low-level arguments to high level arguments. Namely,
|
||||||
|
/// all of ripgrep's positional arguments are treated as file paths, except
|
||||||
|
/// in certain circumstances where the first argument is treated as a regex
|
||||||
|
/// pattern.
|
||||||
|
///
|
||||||
|
/// Note that each implementation of this trait requires a long flag name,
|
||||||
|
/// but can also optionally have a short version and even a negation flag.
|
||||||
|
/// For example, the `-E/--encoding` flag accepts a value, but it also has a
|
||||||
|
/// `--no-encoding` negation flag for reverting back to "automatic" encoding
|
||||||
|
/// detection. All three of `-E`, `--encoding` and `--no-encoding` are provided
|
||||||
|
/// by a single implementation of this trait.
|
||||||
|
///
|
||||||
|
/// ripgrep only supports flags that are switches or flags that accept a single
|
||||||
|
/// value. Flags that accept multiple values are an unsupported abberation.
|
||||||
|
trait Flag: Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static {
|
||||||
|
/// Returns true if this flag is a switch. When a flag is a switch, the
|
||||||
|
/// CLI parser will not look for a value after the flag is seen.
|
||||||
|
fn is_switch(&self) -> bool;
|
||||||
|
|
||||||
|
/// A short single byte name for this flag. This returns `None` by default,
|
||||||
|
/// which signifies that the flag has no short name.
|
||||||
|
///
|
||||||
|
/// The byte returned must be an ASCII codepoint that is a `.` or is
|
||||||
|
/// alpha-numeric.
|
||||||
|
fn name_short(&self) -> Option<u8> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the long name of this flag. All flags must have a "long" name.
|
||||||
|
///
|
||||||
|
/// The long name must be at least 2 bytes, and all of its bytes must be
|
||||||
|
/// ASCII codepoints that are either `-` or alpha-numeric.
|
||||||
|
fn name_long(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Returns a list of aliases for this flag.
|
||||||
|
///
|
||||||
|
/// The aliases must follow the same rules as `Flag::name_long`.
|
||||||
|
///
|
||||||
|
/// By default, an empty slice is returned.
|
||||||
|
fn aliases(&self) -> &'static [&'static str] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a negated name for this flag. The negation of a flag is
|
||||||
|
/// intended to have the opposite meaning of a flag or to otherwise turn
|
||||||
|
/// something "off" or revert it to its default behavior.
|
||||||
|
///
|
||||||
|
/// Negated flags are not listed in their own section in the `-h/--help`
|
||||||
|
/// output or man page. Instead, they are automatically mentioned at the
|
||||||
|
/// end of the documentation section of the flag they negated.
|
||||||
|
///
|
||||||
|
/// The aliases must follow the same rules as `Flag::name_long`.
|
||||||
|
///
|
||||||
|
/// By default, a flag has no negation and this returns `None`.
|
||||||
|
fn name_negated(&self) -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the variable name describing the type of value this flag
|
||||||
|
/// accepts. This should always be set for non-switch flags and never set
|
||||||
|
/// for switch flags.
|
||||||
|
///
|
||||||
|
/// For example, the `--max-count` flag has its variable name set to `NUM`.
|
||||||
|
///
|
||||||
|
/// The convention is to capitalize variable names.
|
||||||
|
///
|
||||||
|
/// By default this returns `None`.
|
||||||
|
fn doc_variable(&self) -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the category of this flag.
|
||||||
|
///
|
||||||
|
/// Every flag must have a single category. Categories are used to organize
|
||||||
|
/// flags in the generated documentation.
|
||||||
|
fn doc_category(&self) -> Category;
|
||||||
|
|
||||||
|
/// A (very) short documentation string describing what this flag does.
|
||||||
|
///
|
||||||
|
/// This may sacrifice "proper English" in order to be as terse as
|
||||||
|
/// possible. Generally, we try to ensure that `rg -h` doesn't have any
|
||||||
|
/// lines that exceed 79 columns.
|
||||||
|
fn doc_short(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// A (possibly very) longer documentation string describing in full
|
||||||
|
/// detail what this flag does. This should be in mandoc/mdoc format.
|
||||||
|
fn doc_long(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// If this is a non-switch flag that accepts a small set of specific
|
||||||
|
/// values, then this should list them.
|
||||||
|
///
|
||||||
|
/// This returns an empty slice by default.
|
||||||
|
fn doc_choices(&self) -> &'static [&'static str] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn completion_type(&self) -> CompletionType {
|
||||||
|
CompletionType::Other
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given the parsed value (which might just be a switch), this should
|
||||||
|
/// update the state in `args` based on the value given for this flag.
|
||||||
|
///
|
||||||
|
/// This may update state for other flags as appropriate.
|
||||||
|
///
|
||||||
|
/// The `-V/--version` and `-h/--help` flags are treated specially in the
|
||||||
|
/// parser and should do nothing here.
|
||||||
|
///
|
||||||
|
/// By convention, implementations should generally not try to "do"
|
||||||
|
/// anything other than validate the value given. For example, the
|
||||||
|
/// implementation for `--hostname-bin` should not try to resolve the
|
||||||
|
/// hostname to use by running the binary provided. That should be saved
|
||||||
|
/// for a later step. This convention is used to ensure that getting the
|
||||||
|
/// low-level arguments is as reliable and quick as possible. It also
|
||||||
|
/// ensures that "doing something" occurs a minimal number of times. For
|
||||||
|
/// example, by avoiding trying to find the hostname here, we can do it
|
||||||
|
/// once later no matter how many times `--hostname-bin` is provided.
|
||||||
|
///
|
||||||
|
/// Implementations should not include the flag name in the error message
|
||||||
|
/// returned. The flag name is included automatically by the parser.
|
||||||
|
fn update(
|
||||||
|
&self,
|
||||||
|
value: FlagValue,
|
||||||
|
args: &mut crate::flags::lowargs::LowArgs,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The category that a flag belongs to.
|
||||||
|
///
|
||||||
|
/// Categories are used to organize flags into "logical" groups in the
|
||||||
|
/// generated documentation.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
|
enum Category {
|
||||||
|
/// Flags related to how ripgrep reads its input. Its "input" generally
|
||||||
|
/// consists of the patterns it is trying to match and the haystacks it is
|
||||||
|
/// trying to search.
|
||||||
|
Input,
|
||||||
|
/// Flags related to the operation of the search itself. For example,
|
||||||
|
/// whether case insensitive matching is enabled.
|
||||||
|
Search,
|
||||||
|
/// Flags related to how ripgrep filters haystacks. For example, whether
|
||||||
|
/// to respect gitignore files or not.
|
||||||
|
Filter,
|
||||||
|
/// Flags related to how ripgrep shows its search results. For example,
|
||||||
|
/// whether to show line numbers or not.
|
||||||
|
Output,
|
||||||
|
/// Flags related to changing ripgrep's output at a more fundamental level.
|
||||||
|
/// For example, flags like `--count` suppress printing of individual
|
||||||
|
/// lines, and instead just print the total count of matches for each file
|
||||||
|
/// searched.
|
||||||
|
OutputModes,
|
||||||
|
/// Flags related to logging behavior such as emitting non-fatal error
|
||||||
|
/// messages or printing search statistics.
|
||||||
|
Logging,
|
||||||
|
/// Other behaviors not related to ripgrep's core functionality. For
|
||||||
|
/// example, printing the file type globbing rules, or printing the list
|
||||||
|
/// of files ripgrep would search without actually searching them.
|
||||||
|
OtherBehaviors,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Category {
|
||||||
|
/// Returns a string representation of this category.
|
||||||
|
///
|
||||||
|
/// This string is the name of the variable used in various templates for
|
||||||
|
/// generated documentation. This name can be used for interpolation.
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
Category::Input => "input",
|
||||||
|
Category::Search => "search",
|
||||||
|
Category::Filter => "filter",
|
||||||
|
Category::Output => "output",
|
||||||
|
Category::OutputModes => "output-modes",
|
||||||
|
Category::Logging => "logging",
|
||||||
|
Category::OtherBehaviors => "other-behaviors",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of argument a flag accepts, to be used for shell completions.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
enum CompletionType {
|
||||||
|
/// No special category. is_switch() and doc_choices() may apply.
|
||||||
|
Other,
|
||||||
|
/// A path to a file.
|
||||||
|
Filename,
|
||||||
|
/// A command in $PATH.
|
||||||
|
Executable,
|
||||||
|
/// The name of a file type, as used by e.g. --type.
|
||||||
|
Filetype,
|
||||||
|
/// The name of an encoding_rs encoding, as used by --encoding.
|
||||||
|
Encoding,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a value parsed from the command line.
|
||||||
|
///
|
||||||
|
/// This doesn't include the corresponding flag, but values come in one of
|
||||||
|
/// two forms: a switch (on or off) or an arbitrary value.
|
||||||
|
///
|
||||||
|
/// Note that the CLI doesn't directly support negated switches. For example,
|
||||||
|
/// you can'd do anything like `-n=false` or any of that nonsense. Instead,
|
||||||
|
/// the CLI parser knows about which flag names are negations and which aren't
|
||||||
|
/// (courtesy of the `Flag` trait). If a flag given is known as a negation,
|
||||||
|
/// then a `FlagValue::Switch(false)` value is passed into `Flag::update`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum FlagValue {
|
||||||
|
/// A flag that is either on or off.
|
||||||
|
Switch(bool),
|
||||||
|
/// A flag that comes with an arbitrary user value.
|
||||||
|
Value(OsString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlagValue {
|
||||||
|
/// Return the yes or no value of this switch.
|
||||||
|
///
|
||||||
|
/// If this flag value is not a switch, then this panics.
|
||||||
|
///
|
||||||
|
/// This is useful when writing the implementation of `Flag::update`.
|
||||||
|
/// namely, callers usually know whether a switch or a value is expected.
|
||||||
|
/// If a flag is something different, then it indicates a bug, and thus a
|
||||||
|
/// panic is acceptable.
|
||||||
|
fn unwrap_switch(self) -> bool {
|
||||||
|
match self {
|
||||||
|
FlagValue::Switch(yes) => yes,
|
||||||
|
FlagValue::Value(_) => {
|
||||||
|
unreachable!("got flag value but expected switch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the user provided value of this flag.
|
||||||
|
///
|
||||||
|
/// If this flag is a switch, then this panics.
|
||||||
|
///
|
||||||
|
/// This is useful when writing the implementation of `Flag::update`.
|
||||||
|
/// namely, callers usually know whether a switch or a value is expected.
|
||||||
|
/// If a flag is something different, then it indicates a bug, and thus a
|
||||||
|
/// panic is acceptable.
|
||||||
|
fn unwrap_value(self) -> OsString {
|
||||||
|
match self {
|
||||||
|
FlagValue::Switch(_) => {
|
||||||
|
unreachable!("got switch but expected flag value")
|
||||||
|
}
|
||||||
|
FlagValue::Value(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
476
crates/core/flags/parse.rs
Normal file
476
crates/core/flags/parse.rs
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
/*!
|
||||||
|
Parses command line arguments into a structured and typed representation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{borrow::Cow, collections::BTreeSet, ffi::OsString};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
use crate::flags::{
|
||||||
|
defs::FLAGS,
|
||||||
|
hiargs::HiArgs,
|
||||||
|
lowargs::{LoggingMode, LowArgs, SpecialMode},
|
||||||
|
Flag, FlagValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The result of parsing CLI arguments.
|
||||||
|
///
|
||||||
|
/// This is basically a `anyhow::Result<T>`, but with one extra variant that is
|
||||||
|
/// inhabited whenever ripgrep should execute a "special" mode. That is, when a
|
||||||
|
/// user provides the `-h/--help` or `-V/--version` flags.
|
||||||
|
///
|
||||||
|
/// This special variant exists to allow CLI parsing to short circuit as
|
||||||
|
/// quickly as is reasonable. For example, it lets CLI parsing avoid reading
|
||||||
|
/// ripgrep's configuration and converting low level arguments into a higher
|
||||||
|
/// level representation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum ParseResult<T> {
|
||||||
|
Special(SpecialMode),
|
||||||
|
Ok(T),
|
||||||
|
Err(anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ParseResult<T> {
|
||||||
|
/// If this result is `Ok`, then apply `then` to it. Otherwise, return this
|
||||||
|
/// result unchanged.
|
||||||
|
fn and_then<U>(
|
||||||
|
self,
|
||||||
|
mut then: impl FnMut(T) -> ParseResult<U>,
|
||||||
|
) -> ParseResult<U> {
|
||||||
|
match self {
|
||||||
|
ParseResult::Special(mode) => ParseResult::Special(mode),
|
||||||
|
ParseResult::Ok(t) => then(t),
|
||||||
|
ParseResult::Err(err) => ParseResult::Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse CLI arguments and convert then to their high level representation.
|
||||||
|
pub(crate) fn parse() -> ParseResult<HiArgs> {
|
||||||
|
parse_low().and_then(|low| match HiArgs::from_low_args(low) {
|
||||||
|
Ok(hi) => ParseResult::Ok(hi),
|
||||||
|
Err(err) => ParseResult::Err(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse CLI arguments only into their low level representation.
|
||||||
|
///
|
||||||
|
/// This takes configuration into account. That is, it will try to read
|
||||||
|
/// `RIPGREP_CONFIG_PATH` and prepend any arguments found there to the
|
||||||
|
/// arguments passed to this process.
|
||||||
|
///
|
||||||
|
/// This will also set one-time global state flags, such as the log level and
|
||||||
|
/// whether messages should be printed.
|
||||||
|
fn parse_low() -> ParseResult<LowArgs> {
|
||||||
|
if let Err(err) = crate::logger::Logger::init() {
|
||||||
|
let err = anyhow::anyhow!("failed to initialize logger: {err}");
|
||||||
|
return ParseResult::Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parser = Parser::new();
|
||||||
|
let mut low = LowArgs::default();
|
||||||
|
if let Err(err) = parser.parse(std::env::args_os().skip(1), &mut low) {
|
||||||
|
return ParseResult::Err(err);
|
||||||
|
}
|
||||||
|
// Even though we haven't parsed the config file yet (assuming it exists),
|
||||||
|
// we can still use the arguments given on the CLI to setup ripgrep's
|
||||||
|
// logging preferences. Even if the config file changes them in some way,
|
||||||
|
// it's really the best we can do. This way, for example, folks can pass
|
||||||
|
// `--trace` and see any messages logged during config file parsing.
|
||||||
|
set_log_levels(&low);
|
||||||
|
// Before we try to take configuration into account, we can bail early
|
||||||
|
// if a special mode was enabled. This is basically only for version and
|
||||||
|
// help output which shouldn't be impacted by extra configuration.
|
||||||
|
if let Some(special) = low.special.take() {
|
||||||
|
return ParseResult::Special(special);
|
||||||
|
}
|
||||||
|
// If the end user says no config, then respect it.
|
||||||
|
if low.no_config {
|
||||||
|
log::debug!("not reading config files because --no-config is present");
|
||||||
|
return ParseResult::Ok(low);
|
||||||
|
}
|
||||||
|
// Look for arguments from a config file. If we got nothing (whether the
|
||||||
|
// file is empty or RIPGREP_CONFIG_PATH wasn't set), then we don't need
|
||||||
|
// to re-parse.
|
||||||
|
let config_args = crate::flags::config::args();
|
||||||
|
if config_args.is_empty() {
|
||||||
|
log::debug!("no extra arguments found from configuration file");
|
||||||
|
return ParseResult::Ok(low);
|
||||||
|
}
|
||||||
|
// The final arguments are just the arguments from the CLI appending to
|
||||||
|
// the end of the config arguments.
|
||||||
|
let mut final_args = config_args;
|
||||||
|
final_args.extend(std::env::args_os().skip(1));
|
||||||
|
|
||||||
|
// Now do the CLI parsing dance again.
|
||||||
|
let mut low = LowArgs::default();
|
||||||
|
if let Err(err) = parser.parse(final_args.into_iter(), &mut low) {
|
||||||
|
return ParseResult::Err(err);
|
||||||
|
}
|
||||||
|
// Reset the message and logging levels, since they could have changed.
|
||||||
|
set_log_levels(&low);
|
||||||
|
ParseResult::Ok(low)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets global state flags that control logging based on low-level arguments.
|
||||||
|
fn set_log_levels(low: &LowArgs) {
|
||||||
|
crate::messages::set_messages(!low.no_messages);
|
||||||
|
crate::messages::set_ignore_messages(!low.no_ignore_messages);
|
||||||
|
match low.logging {
|
||||||
|
Some(LoggingMode::Trace) => {
|
||||||
|
log::set_max_level(log::LevelFilter::Trace)
|
||||||
|
}
|
||||||
|
Some(LoggingMode::Debug) => {
|
||||||
|
log::set_max_level(log::LevelFilter::Debug)
|
||||||
|
}
|
||||||
|
None => log::set_max_level(log::LevelFilter::Warn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the sequence of CLI arguments given a low level typed set of
|
||||||
|
/// arguments.
|
||||||
|
///
|
||||||
|
/// This is exposed for testing that the correct low-level arguments are parsed
|
||||||
|
/// from a CLI. It just runs the parser once over the CLI arguments. It doesn't
|
||||||
|
/// setup logging or read from a config file.
|
||||||
|
///
|
||||||
|
/// This assumes the iterator given does *not* begin with the binary name.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn parse_low_raw(
|
||||||
|
rawargs: impl IntoIterator<Item = impl Into<OsString>>,
|
||||||
|
) -> anyhow::Result<LowArgs> {
|
||||||
|
let mut args = LowArgs::default();
|
||||||
|
Parser::new().parse(rawargs, &mut args)?;
|
||||||
|
Ok(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the metadata for the flag of the given name.
|
||||||
|
pub(super) fn lookup(name: &str) -> Option<&'static dyn Flag> {
|
||||||
|
// N.B. Creating a new parser might look expensive, but it only builds
|
||||||
|
// the lookup trie exactly once. That is, we get a `&'static Parser` from
|
||||||
|
// `Parser::new()`.
|
||||||
|
match Parser::new().find_long(name) {
|
||||||
|
FlagLookup::Match(&FlagInfo { flag, .. }) => Some(flag),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A parser for turning a sequence of command line arguments into a more
|
||||||
|
/// strictly typed set of arguments.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Parser {
|
||||||
|
/// A single map that contains all possible flag names. This includes
|
||||||
|
/// short and long names, aliases and negations. This maps those names to
|
||||||
|
/// indices into `info`.
|
||||||
|
map: FlagMap,
|
||||||
|
/// A map from IDs returned by the `map` to the corresponding flag
|
||||||
|
/// information.
|
||||||
|
info: Vec<FlagInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
/// Create a new parser.
|
||||||
|
///
|
||||||
|
/// This always creates the same parser and only does it once. Callers may
|
||||||
|
/// call this repeatedly, and the parser will only be built once.
|
||||||
|
fn new() -> &'static Parser {
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
// Since a parser's state is immutable and completely determined by
|
||||||
|
// FLAGS, and since FLAGS is a constant, we can initialize it exactly
|
||||||
|
// once.
|
||||||
|
static P: OnceLock<Parser> = OnceLock::new();
|
||||||
|
P.get_or_init(|| {
|
||||||
|
let mut infos = vec![];
|
||||||
|
for &flag in FLAGS.iter() {
|
||||||
|
infos.push(FlagInfo {
|
||||||
|
flag,
|
||||||
|
name: Ok(flag.name_long()),
|
||||||
|
kind: FlagInfoKind::Standard,
|
||||||
|
});
|
||||||
|
for alias in flag.aliases() {
|
||||||
|
infos.push(FlagInfo {
|
||||||
|
flag,
|
||||||
|
name: Ok(alias),
|
||||||
|
kind: FlagInfoKind::Alias,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(byte) = flag.name_short() {
|
||||||
|
infos.push(FlagInfo {
|
||||||
|
flag,
|
||||||
|
name: Err(byte),
|
||||||
|
kind: FlagInfoKind::Standard,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(name) = flag.name_negated() {
|
||||||
|
infos.push(FlagInfo {
|
||||||
|
flag,
|
||||||
|
name: Ok(name),
|
||||||
|
kind: FlagInfoKind::Negated,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let map = FlagMap::new(&infos);
|
||||||
|
Parser { map, info: infos }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the given CLI arguments into a low level representation.
|
||||||
|
///
|
||||||
|
/// The iterator given should *not* start with the binary name.
|
||||||
|
fn parse<I, O>(&self, rawargs: I, args: &mut LowArgs) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = O>,
|
||||||
|
O: Into<OsString>,
|
||||||
|
{
|
||||||
|
let mut p = lexopt::Parser::from_args(rawargs);
|
||||||
|
while let Some(arg) = p.next().context("invalid CLI arguments")? {
|
||||||
|
let lookup = match arg {
|
||||||
|
lexopt::Arg::Value(value) => {
|
||||||
|
args.positional.push(value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lexopt::Arg::Short(ch) if ch == 'h' => {
|
||||||
|
// Special case -h/--help since behavior is different
|
||||||
|
// based on whether short or long flag is given.
|
||||||
|
args.special = Some(SpecialMode::HelpShort);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lexopt::Arg::Short(ch) if ch == 'V' => {
|
||||||
|
// Special case -V/--version since behavior is different
|
||||||
|
// based on whether short or long flag is given.
|
||||||
|
args.special = Some(SpecialMode::VersionShort);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lexopt::Arg::Short(ch) => self.find_short(ch),
|
||||||
|
lexopt::Arg::Long(name) if name == "help" => {
|
||||||
|
// Special case -h/--help since behavior is different
|
||||||
|
// based on whether short or long flag is given.
|
||||||
|
args.special = Some(SpecialMode::HelpLong);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lexopt::Arg::Long(name) if name == "version" => {
|
||||||
|
// Special case -V/--version since behavior is different
|
||||||
|
// based on whether short or long flag is given.
|
||||||
|
args.special = Some(SpecialMode::VersionLong);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lexopt::Arg::Long(name) => self.find_long(name),
|
||||||
|
};
|
||||||
|
let mat = match lookup {
|
||||||
|
FlagLookup::Match(mat) => mat,
|
||||||
|
FlagLookup::UnrecognizedShort(name) => {
|
||||||
|
anyhow::bail!("unrecognized flag -{name}")
|
||||||
|
}
|
||||||
|
FlagLookup::UnrecognizedLong(name) => {
|
||||||
|
let mut msg = format!("unrecognized flag --{name}");
|
||||||
|
if let Some(suggest_msg) = suggest(&name) {
|
||||||
|
msg = format!("{msg}\n\n{suggest_msg}");
|
||||||
|
}
|
||||||
|
anyhow::bail!("{msg}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let value = if matches!(mat.kind, FlagInfoKind::Negated) {
|
||||||
|
// Negated flags are always switches, even if the non-negated
|
||||||
|
// flag is not. For example, --context-separator accepts a
|
||||||
|
// value, but --no-context-separator does not.
|
||||||
|
FlagValue::Switch(false)
|
||||||
|
} else if mat.flag.is_switch() {
|
||||||
|
FlagValue::Switch(true)
|
||||||
|
} else {
|
||||||
|
FlagValue::Value(p.value().with_context(|| {
|
||||||
|
format!("missing value for flag {mat}")
|
||||||
|
})?)
|
||||||
|
};
|
||||||
|
mat.flag
|
||||||
|
.update(value, args)
|
||||||
|
.with_context(|| format!("error parsing flag {mat}"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look for a flag by its short name.
|
||||||
|
fn find_short(&self, ch: char) -> FlagLookup<'_> {
|
||||||
|
if !ch.is_ascii() {
|
||||||
|
return FlagLookup::UnrecognizedShort(ch);
|
||||||
|
}
|
||||||
|
let byte = u8::try_from(ch).unwrap();
|
||||||
|
let Some(index) = self.map.find(&[byte]) else {
|
||||||
|
return FlagLookup::UnrecognizedShort(ch);
|
||||||
|
};
|
||||||
|
FlagLookup::Match(&self.info[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look for a flag by its long name.
|
||||||
|
///
|
||||||
|
/// This also works for aliases and negated names.
|
||||||
|
fn find_long(&self, name: &str) -> FlagLookup<'_> {
|
||||||
|
let Some(index) = self.map.find(name.as_bytes()) else {
|
||||||
|
return FlagLookup::UnrecognizedLong(name.to_string());
|
||||||
|
};
|
||||||
|
FlagLookup::Match(&self.info[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of looking up a flag name.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum FlagLookup<'a> {
|
||||||
|
/// Lookup found a match and the metadata for the flag is attached.
|
||||||
|
Match(&'a FlagInfo),
|
||||||
|
/// The given short name is unrecognized.
|
||||||
|
UnrecognizedShort(char),
|
||||||
|
/// The given long name is unrecognized.
|
||||||
|
UnrecognizedLong(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The info about a flag associated with a flag's ID in the flag map.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FlagInfo {
|
||||||
|
/// The flag object and its associated metadata.
|
||||||
|
flag: &'static dyn Flag,
|
||||||
|
/// The actual name that is stored in the Aho-Corasick automaton. When this
|
||||||
|
/// is a byte, it corresponds to a short single character ASCII flag. The
|
||||||
|
/// actual pattern that's in the Aho-Corasick automaton is just the single
|
||||||
|
/// byte.
|
||||||
|
name: Result<&'static str, u8>,
|
||||||
|
/// The type of flag that is stored for the corresponding Aho-Corasick
|
||||||
|
/// pattern.
|
||||||
|
kind: FlagInfoKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of flag that is being matched.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum FlagInfoKind {
|
||||||
|
/// A standard flag, e.g., --passthru.
|
||||||
|
Standard,
|
||||||
|
/// A negation of a standard flag, e.g., --no-multiline.
|
||||||
|
Negated,
|
||||||
|
/// An alias for a standard flag, e.g., --passthrough.
|
||||||
|
Alias,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for FlagInfo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self.name {
|
||||||
|
Ok(long) => write!(f, "--{long}"),
|
||||||
|
Err(short) => write!(f, "-{short}", short = char::from(short)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A map from flag names (short, long, negated and aliases) to their ID.
|
||||||
|
///
|
||||||
|
/// Once an ID is known, it can be used to look up a flag's metadata in the
|
||||||
|
/// parser's internal state.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FlagMap {
|
||||||
|
map: std::collections::HashMap<Vec<u8>, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlagMap {
|
||||||
|
/// Create a new map of flags for the given flag information.
|
||||||
|
///
|
||||||
|
/// The index of each flag info corresponds to its ID.
|
||||||
|
fn new(infos: &[FlagInfo]) -> FlagMap {
|
||||||
|
let mut map = std::collections::HashMap::with_capacity(infos.len());
|
||||||
|
for (i, info) in infos.iter().enumerate() {
|
||||||
|
match info.name {
|
||||||
|
Ok(name) => {
|
||||||
|
assert_eq!(None, map.insert(name.as_bytes().to_vec(), i));
|
||||||
|
}
|
||||||
|
Err(byte) => {
|
||||||
|
assert_eq!(None, map.insert(vec![byte], i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FlagMap { map }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look for a match of `name` in the given Aho-Corasick automaton.
|
||||||
|
///
|
||||||
|
/// This only returns a match if the one found has a length equivalent to
|
||||||
|
/// the length of the name given.
|
||||||
|
fn find(&self, name: &[u8]) -> Option<usize> {
|
||||||
|
self.map.get(name).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possibly return a message suggesting flags similar in the name to the one
|
||||||
|
/// given.
|
||||||
|
///
|
||||||
|
/// The one given should be a flag given by the user (without the leading
|
||||||
|
/// dashes) that was unrecognized. This attempts to find existing flags that
|
||||||
|
/// are similar to the one given.
|
||||||
|
fn suggest(unrecognized: &str) -> Option<String> {
|
||||||
|
let similars = find_similar_names(unrecognized);
|
||||||
|
if similars.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let list = similars
|
||||||
|
.into_iter()
|
||||||
|
.map(|name| format!("--{name}"))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
Some(format!("similar flags that are available: {list}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a sequence of names similar to the unrecognized name given.
|
||||||
|
fn find_similar_names(unrecognized: &str) -> Vec<&'static str> {
|
||||||
|
// The jaccard similarity threshold at which we consider two flag names
|
||||||
|
// similar enough that it's worth suggesting it to the end user.
|
||||||
|
//
|
||||||
|
// This value was determined by some ad hoc experimentation. It might need
|
||||||
|
// further tweaking.
|
||||||
|
const THRESHOLD: f64 = 0.4;
|
||||||
|
|
||||||
|
let mut similar = vec![];
|
||||||
|
let bow_given = ngrams(unrecognized);
|
||||||
|
for &flag in FLAGS.iter() {
|
||||||
|
let name = flag.name_long();
|
||||||
|
let bow = ngrams(name);
|
||||||
|
if jaccard_index(&bow_given, &bow) >= THRESHOLD {
|
||||||
|
similar.push(name);
|
||||||
|
}
|
||||||
|
if let Some(name) = flag.name_negated() {
|
||||||
|
let bow = ngrams(name);
|
||||||
|
if jaccard_index(&bow_given, &bow) >= THRESHOLD {
|
||||||
|
similar.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name in flag.aliases() {
|
||||||
|
let bow = ngrams(name);
|
||||||
|
if jaccard_index(&bow_given, &bow) >= THRESHOLD {
|
||||||
|
similar.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
similar
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A "bag of words" is a set of ngrams.
|
||||||
|
type BagOfWords<'a> = BTreeSet<Cow<'a, [u8]>>;
|
||||||
|
|
||||||
|
/// Returns the jaccard index (a measure of similarity) between sets of ngrams.
|
||||||
|
fn jaccard_index(ngrams1: &BagOfWords<'_>, ngrams2: &BagOfWords<'_>) -> f64 {
|
||||||
|
let union = u32::try_from(ngrams1.union(ngrams2).count())
|
||||||
|
.expect("fewer than u32::MAX flags");
|
||||||
|
let intersection = u32::try_from(ngrams1.intersection(ngrams2).count())
|
||||||
|
.expect("fewer than u32::MAX flags");
|
||||||
|
f64::from(intersection) / f64::from(union)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all 3-grams in the slice given.
|
||||||
|
///
|
||||||
|
/// If the slice doesn't contain a 3-gram, then one is artificially created by
|
||||||
|
/// padding it out with a character that will never appear in a flag name.
|
||||||
|
fn ngrams(flag_name: &str) -> BagOfWords<'_> {
|
||||||
|
// We only allow ASCII flag names, so we can just use bytes.
|
||||||
|
let slice = flag_name.as_bytes();
|
||||||
|
let seq: Vec<Cow<[u8]>> = match slice.len() {
|
||||||
|
0 => vec![Cow::Owned(b"!!!".to_vec())],
|
||||||
|
1 => vec![Cow::Owned(vec![slice[0], b'!', b'!'])],
|
||||||
|
2 => vec![Cow::Owned(vec![slice[0], slice[1], b'!'])],
|
||||||
|
_ => slice.windows(3).map(Cow::Borrowed).collect(),
|
||||||
|
};
|
||||||
|
BTreeSet::from_iter(seq)
|
||||||
|
}
|
@ -1,111 +1,111 @@
|
|||||||
|
/*!
|
||||||
|
Defines a builder for haystacks.
|
||||||
|
|
||||||
|
A "haystack" represents something we want to search. It encapsulates the logic
|
||||||
|
for whether a haystack ought to be searched or not, separate from the standard
|
||||||
|
ignore rules and other filtering logic.
|
||||||
|
|
||||||
|
Effectively, a haystack wraps a directory entry and adds some light application
|
||||||
|
level logic around it.
|
||||||
|
*/
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use ignore::{self, DirEntry};
|
|
||||||
use log;
|
|
||||||
|
|
||||||
/// A configuration for describing how subjects should be built.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Config {
|
|
||||||
strip_dot_prefix: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Config {
|
|
||||||
Config { strip_dot_prefix: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A builder for constructing things to search over.
|
/// A builder for constructing things to search over.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SubjectBuilder {
|
pub(crate) struct HaystackBuilder {
|
||||||
config: Config,
|
strip_dot_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubjectBuilder {
|
impl HaystackBuilder {
|
||||||
/// Return a new subject builder with a default configuration.
|
/// Return a new haystack builder with a default configuration.
|
||||||
pub fn new() -> SubjectBuilder {
|
pub(crate) fn new() -> HaystackBuilder {
|
||||||
SubjectBuilder { config: Config::default() }
|
HaystackBuilder { strip_dot_prefix: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new subject from a possibly missing directory entry.
|
/// Create a new haystack from a possibly missing directory entry.
|
||||||
///
|
///
|
||||||
/// If the directory entry isn't present, then the corresponding error is
|
/// If the directory entry isn't present, then the corresponding error is
|
||||||
/// logged if messages have been configured. Otherwise, if the subject is
|
/// logged if messages have been configured. Otherwise, if the directory
|
||||||
/// deemed searchable, then it is returned.
|
/// entry is deemed searchable, then it is returned as a haystack.
|
||||||
pub fn build_from_result(
|
pub(crate) fn build_from_result(
|
||||||
&self,
|
&self,
|
||||||
result: Result<DirEntry, ignore::Error>,
|
result: Result<ignore::DirEntry, ignore::Error>,
|
||||||
) -> Option<Subject> {
|
) -> Option<Haystack> {
|
||||||
match result {
|
match result {
|
||||||
Ok(dent) => self.build(dent),
|
Ok(dent) => self.build(dent),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
err_message!("{}", err);
|
err_message!("{err}");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new subject using this builder's configuration.
|
/// Create a new haystack using this builder's configuration.
|
||||||
///
|
///
|
||||||
/// If a subject could not be created or should otherwise not be searched,
|
/// If a directory entry could not be created or should otherwise not be
|
||||||
/// then this returns `None` after emitting any relevant log messages.
|
/// searched, then this returns `None` after emitting any relevant log
|
||||||
pub fn build(&self, dent: DirEntry) -> Option<Subject> {
|
/// messages.
|
||||||
let subj =
|
fn build(&self, dent: ignore::DirEntry) -> Option<Haystack> {
|
||||||
Subject { dent, strip_dot_prefix: self.config.strip_dot_prefix };
|
let hay = Haystack { dent, strip_dot_prefix: self.strip_dot_prefix };
|
||||||
if let Some(ignore_err) = subj.dent.error() {
|
if let Some(err) = hay.dent.error() {
|
||||||
ignore_message!("{}", ignore_err);
|
ignore_message!("{err}");
|
||||||
}
|
}
|
||||||
// If this entry was explicitly provided by an end user, then we always
|
// If this entry was explicitly provided by an end user, then we always
|
||||||
// want to search it.
|
// want to search it.
|
||||||
if subj.is_explicit() {
|
if hay.is_explicit() {
|
||||||
return Some(subj);
|
return Some(hay);
|
||||||
}
|
}
|
||||||
// At this point, we only want to search something if it's explicitly a
|
// At this point, we only want to search something if it's explicitly a
|
||||||
// file. This omits symlinks. (If ripgrep was configured to follow
|
// file. This omits symlinks. (If ripgrep was configured to follow
|
||||||
// symlinks, then they have already been followed by the directory
|
// symlinks, then they have already been followed by the directory
|
||||||
// traversal.)
|
// traversal.)
|
||||||
if subj.is_file() {
|
if hay.is_file() {
|
||||||
return Some(subj);
|
return Some(hay);
|
||||||
}
|
}
|
||||||
// We got nothin. Emit a debug message, but only if this isn't a
|
// We got nothing. Emit a debug message, but only if this isn't a
|
||||||
// directory. Otherwise, emitting messages for directories is just
|
// directory. Otherwise, emitting messages for directories is just
|
||||||
// noisy.
|
// noisy.
|
||||||
if !subj.is_dir() {
|
if !hay.is_dir() {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"ignoring {}: failed to pass subject filter: \
|
"ignoring {}: failed to pass haystack filter: \
|
||||||
file type: {:?}, metadata: {:?}",
|
file type: {:?}, metadata: {:?}",
|
||||||
subj.dent.path().display(),
|
hay.dent.path().display(),
|
||||||
subj.dent.file_type(),
|
hay.dent.file_type(),
|
||||||
subj.dent.metadata()
|
hay.dent.metadata()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When enabled, if the subject's file path starts with `./` then it is
|
/// When enabled, if the haystack's file path starts with `./` then it is
|
||||||
/// stripped.
|
/// stripped.
|
||||||
///
|
///
|
||||||
/// This is useful when implicitly searching the current working directory.
|
/// This is useful when implicitly searching the current working directory.
|
||||||
pub fn strip_dot_prefix(&mut self, yes: bool) -> &mut SubjectBuilder {
|
pub(crate) fn strip_dot_prefix(
|
||||||
self.config.strip_dot_prefix = yes;
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
|
) -> &mut HaystackBuilder {
|
||||||
|
self.strip_dot_prefix = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A subject is a thing we want to search. Generally, a subject is either a
|
/// A haystack is a thing we want to search.
|
||||||
/// file or stdin.
|
///
|
||||||
|
/// Generally, a haystack is either a file or stdin.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Subject {
|
pub(crate) struct Haystack {
|
||||||
dent: DirEntry,
|
dent: ignore::DirEntry,
|
||||||
strip_dot_prefix: bool,
|
strip_dot_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subject {
|
impl Haystack {
|
||||||
/// Return the file path corresponding to this subject.
|
/// Return the file path corresponding to this haystack.
|
||||||
///
|
///
|
||||||
/// If this subject corresponds to stdin, then a special `<stdin>` path
|
/// If this haystack corresponds to stdin, then a special `<stdin>` path
|
||||||
/// is returned instead.
|
/// is returned instead.
|
||||||
pub fn path(&self) -> &Path {
|
pub(crate) fn path(&self) -> &Path {
|
||||||
if self.strip_dot_prefix && self.dent.path().starts_with("./") {
|
if self.strip_dot_prefix && self.dent.path().starts_with("./") {
|
||||||
self.dent.path().strip_prefix("./").unwrap()
|
self.dent.path().strip_prefix("./").unwrap()
|
||||||
} else {
|
} else {
|
||||||
@ -114,21 +114,21 @@ impl Subject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this entry corresponds to stdin.
|
/// Returns true if and only if this entry corresponds to stdin.
|
||||||
pub fn is_stdin(&self) -> bool {
|
pub(crate) fn is_stdin(&self) -> bool {
|
||||||
self.dent.is_stdin()
|
self.dent.is_stdin()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this entry corresponds to a subject to
|
/// Returns true if and only if this entry corresponds to a haystack to
|
||||||
/// search that was explicitly supplied by an end user.
|
/// search that was explicitly supplied by an end user.
|
||||||
///
|
///
|
||||||
/// Generally, this corresponds to either stdin or an explicit file path
|
/// Generally, this corresponds to either stdin or an explicit file path
|
||||||
/// argument. e.g., in `rg foo some-file ./some-dir/`, `some-file` is
|
/// argument. e.g., in `rg foo some-file ./some-dir/`, `some-file` is
|
||||||
/// an explicit subject, but, e.g., `./some-dir/some-other-file` is not.
|
/// an explicit haystack, but, e.g., `./some-dir/some-other-file` is not.
|
||||||
///
|
///
|
||||||
/// However, note that ripgrep does not see through shell globbing. e.g.,
|
/// However, note that ripgrep does not see through shell globbing. e.g.,
|
||||||
/// in `rg foo ./some-dir/*`, `./some-dir/some-other-file` will be treated
|
/// in `rg foo ./some-dir/*`, `./some-dir/some-other-file` will be treated
|
||||||
/// as an explicit subject.
|
/// as an explicit haystack.
|
||||||
pub fn is_explicit(&self) -> bool {
|
pub(crate) fn is_explicit(&self) -> bool {
|
||||||
// stdin is obvious. When an entry has a depth of 0, that means it
|
// stdin is obvious. When an entry has a depth of 0, that means it
|
||||||
// was explicitly provided to our directory iterator, which means it
|
// was explicitly provided to our directory iterator, which means it
|
||||||
// was in turn explicitly provided by the end user. The !is_dir check
|
// was in turn explicitly provided by the end user. The !is_dir check
|
||||||
@ -138,7 +138,7 @@ impl Subject {
|
|||||||
self.is_stdin() || (self.dent.depth() == 0 && !self.is_dir())
|
self.is_stdin() || (self.dent.depth() == 0 && !self.is_dir())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this subject points to a directory after
|
/// Returns true if and only if this haystack points to a directory after
|
||||||
/// following symbolic links.
|
/// following symbolic links.
|
||||||
fn is_dir(&self) -> bool {
|
fn is_dir(&self) -> bool {
|
||||||
let ft = match self.dent.file_type() {
|
let ft = match self.dent.file_type() {
|
||||||
@ -153,7 +153,7 @@ impl Subject {
|
|||||||
self.dent.path_is_symlink() && self.dent.path().is_dir()
|
self.dent.path_is_symlink() && self.dent.path().is_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this subject points to a file.
|
/// Returns true if and only if this haystack points to a file.
|
||||||
fn is_file(&self) -> bool {
|
fn is_file(&self) -> bool {
|
||||||
self.dent.file_type().map_or(false, |ft| ft.is_file())
|
self.dent.file_type().map_or(false, |ft| ft.is_file())
|
||||||
}
|
}
|
@ -1,24 +1,28 @@
|
|||||||
// This module defines a super simple logger that works with the `log` crate.
|
/*!
|
||||||
// We don't need anything fancy; just basic log levels and the ability to
|
Defines a super simple logger that works with the `log` crate.
|
||||||
// print to stderr. We therefore avoid bringing in extra dependencies just
|
|
||||||
// for this functionality.
|
|
||||||
|
|
||||||
use log::{self, Log};
|
We don't do anything fancy. We just need basic log levels and the ability to
|
||||||
|
print to stderr. We therefore avoid bringing in extra dependencies just for
|
||||||
|
this functionality.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use log::Log;
|
||||||
|
|
||||||
/// The simplest possible logger that logs to stderr.
|
/// The simplest possible logger that logs to stderr.
|
||||||
///
|
///
|
||||||
/// This logger does no filtering. Instead, it relies on the `log` crates
|
/// This logger does no filtering. Instead, it relies on the `log` crates
|
||||||
/// filtering via its global max_level setting.
|
/// filtering via its global max_level setting.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Logger(());
|
pub(crate) struct Logger(());
|
||||||
|
|
||||||
|
/// A singleton used as the target for an implementation of the `Log` trait.
|
||||||
const LOGGER: &'static Logger = &Logger(());
|
const LOGGER: &'static Logger = &Logger(());
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
/// Create a new logger that logs to stderr and initialize it as the
|
/// Create a new logger that logs to stderr and initialize it as the
|
||||||
/// global logger. If there was a problem setting the logger, then an
|
/// global logger. If there was a problem setting the logger, then an
|
||||||
/// error is returned.
|
/// error is returned.
|
||||||
pub fn init() -> Result<(), log::SetLoggerError> {
|
pub(crate) fn init() -> Result<(), log::SetLoggerError> {
|
||||||
log::set_logger(LOGGER)
|
log::set_logger(LOGGER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,7 +37,7 @@ impl Log for Logger {
|
|||||||
fn log(&self, record: &log::Record<'_>) {
|
fn log(&self, record: &log::Record<'_>) {
|
||||||
match (record.file(), record.line()) {
|
match (record.file(), record.line()) {
|
||||||
(Some(file), Some(line)) => {
|
(Some(file), Some(line)) => {
|
||||||
eprintln!(
|
eprintln_locked!(
|
||||||
"{}|{}|{}:{}: {}",
|
"{}|{}|{}:{}: {}",
|
||||||
record.level(),
|
record.level(),
|
||||||
record.target(),
|
record.target(),
|
||||||
@ -43,7 +47,7 @@ impl Log for Logger {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
(Some(file), None) => {
|
(Some(file), None) => {
|
||||||
eprintln!(
|
eprintln_locked!(
|
||||||
"{}|{}|{}: {}",
|
"{}|{}|{}: {}",
|
||||||
record.level(),
|
record.level(),
|
||||||
record.target(),
|
record.target(),
|
||||||
@ -52,7 +56,7 @@ impl Log for Logger {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!(
|
eprintln_locked!(
|
||||||
"{}|{}: {}",
|
"{}|{}: {}",
|
||||||
record.level(),
|
record.level(),
|
||||||
record.target(),
|
record.target(),
|
||||||
@ -63,6 +67,6 @@ impl Log for Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) {
|
fn flush(&self) {
|
||||||
// We use eprintln! which is flushed on every call.
|
// We use eprintln_locked! which is flushed on every call.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
use std::error;
|
/*!
|
||||||
use std::io::{self, Write};
|
The main entry point into ripgrep.
|
||||||
use std::process;
|
*/
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::time::Instant;
|
use std::{io::Write, process::ExitCode};
|
||||||
|
|
||||||
use ignore::WalkState;
|
use ignore::WalkState;
|
||||||
|
|
||||||
use args::Args;
|
use crate::flags::{HiArgs, SearchMode};
|
||||||
use subject::Subject;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod messages;
|
mod messages;
|
||||||
|
|
||||||
mod app;
|
mod flags;
|
||||||
mod args;
|
mod haystack;
|
||||||
mod config;
|
|
||||||
mod logger;
|
mod logger;
|
||||||
mod path_printer;
|
|
||||||
mod search;
|
mod search;
|
||||||
mod subject;
|
|
||||||
|
|
||||||
// Since Rust no longer uses jemalloc by default, ripgrep will, by default,
|
// Since Rust no longer uses jemalloc by default, ripgrep will, by default,
|
||||||
// use the system allocator. On Linux, this would normally be glibc's
|
// use the system allocator. On Linux, this would normally be glibc's
|
||||||
@ -43,62 +39,96 @@ mod subject;
|
|||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||||
|
|
||||||
type Result<T> = ::std::result::Result<T, Box<dyn error::Error>>;
|
/// Then, as it was, then again it will be.
|
||||||
|
fn main() -> ExitCode {
|
||||||
fn main() {
|
match run(flags::parse()) {
|
||||||
if let Err(err) = Args::parse().and_then(try_main) {
|
Ok(code) => code,
|
||||||
eprintln!("{}", err);
|
Err(err) => {
|
||||||
process::exit(2);
|
// Look for a broken pipe error. In this case, we generally want
|
||||||
|
// to exit "gracefully" with a success exit code. This matches
|
||||||
|
// existing Unix convention. We need to handle this explicitly
|
||||||
|
// since the Rust runtime doesn't ask for PIPE signals, and thus
|
||||||
|
// we get an I/O error instead. Traditional C Unix applications
|
||||||
|
// quit by getting a PIPE signal that they don't handle, and thus
|
||||||
|
// the unhandled signal causes the process to unceremoniously
|
||||||
|
// terminate.
|
||||||
|
for cause in err.chain() {
|
||||||
|
if let Some(ioerr) = cause.downcast_ref::<std::io::Error>() {
|
||||||
|
if ioerr.kind() == std::io::ErrorKind::BrokenPipe {
|
||||||
|
return ExitCode::from(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln_locked!("{:#}", err);
|
||||||
|
ExitCode::from(2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_main(args: Args) -> Result<()> {
|
/// The main entry point for ripgrep.
|
||||||
use args::Command::*;
|
///
|
||||||
|
/// The given parse result determines ripgrep's behavior. The parse
|
||||||
|
/// result should be the result of parsing CLI arguments in a low level
|
||||||
|
/// representation, and then followed by an attempt to convert them into a
|
||||||
|
/// higher level representation. The higher level representation has some nicer
|
||||||
|
/// abstractions, for example, instead of representing the `-g/--glob` flag
|
||||||
|
/// as a `Vec<String>` (as in the low level representation), the globs are
|
||||||
|
/// converted into a single matcher.
|
||||||
|
fn run(result: crate::flags::ParseResult<HiArgs>) -> anyhow::Result<ExitCode> {
|
||||||
|
use crate::flags::{Mode, ParseResult};
|
||||||
|
|
||||||
let matched = match args.command()? {
|
let args = match result {
|
||||||
Search => search(&args),
|
ParseResult::Err(err) => return Err(err),
|
||||||
SearchParallel => search_parallel(&args),
|
ParseResult::Special(mode) => return special(mode),
|
||||||
SearchNever => Ok(false),
|
ParseResult::Ok(args) => args,
|
||||||
Files => files(&args),
|
};
|
||||||
FilesParallel => files_parallel(&args),
|
let matched = match args.mode() {
|
||||||
Types => types(&args),
|
Mode::Search(_) if !args.matches_possible() => false,
|
||||||
PCRE2Version => pcre2_version(&args),
|
Mode::Search(mode) if args.threads() == 1 => search(&args, mode)?,
|
||||||
}?;
|
Mode::Search(mode) => search_parallel(&args, mode)?,
|
||||||
if matched && (args.quiet() || !messages::errored()) {
|
Mode::Files if args.threads() == 1 => files(&args)?,
|
||||||
process::exit(0)
|
Mode::Files => files_parallel(&args)?,
|
||||||
|
Mode::Types => return types(&args),
|
||||||
|
Mode::Generate(mode) => return generate(mode),
|
||||||
|
};
|
||||||
|
Ok(if matched && (args.quiet() || !messages::errored()) {
|
||||||
|
ExitCode::from(0)
|
||||||
} else if messages::errored() {
|
} else if messages::errored() {
|
||||||
process::exit(2)
|
ExitCode::from(2)
|
||||||
} else {
|
} else {
|
||||||
process::exit(1)
|
ExitCode::from(1)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The top-level entry point for single-threaded search. This recursively
|
/// The top-level entry point for single-threaded search.
|
||||||
/// steps through the file list (current directory by default) and searches
|
///
|
||||||
/// each file sequentially.
|
/// This recursively steps through the file list (current directory by default)
|
||||||
fn search(args: &Args) -> Result<bool> {
|
/// and searches each file sequentially.
|
||||||
let started_at = Instant::now();
|
fn search(args: &HiArgs, mode: SearchMode) -> anyhow::Result<bool> {
|
||||||
let quit_after_match = args.quit_after_match()?;
|
let started_at = std::time::Instant::now();
|
||||||
let subject_builder = args.subject_builder();
|
let haystack_builder = args.haystack_builder();
|
||||||
let mut stats = args.stats()?;
|
let unsorted = args
|
||||||
let mut searcher = args.search_worker(args.stdout())?;
|
.walk_builder()?
|
||||||
|
.build()
|
||||||
|
.filter_map(|result| haystack_builder.build_from_result(result));
|
||||||
|
let haystacks = args.sort(unsorted);
|
||||||
|
|
||||||
let mut matched = false;
|
let mut matched = false;
|
||||||
let mut searched = false;
|
let mut searched = false;
|
||||||
|
let mut stats = args.stats();
|
||||||
for result in args.walker()? {
|
let mut searcher = args.search_worker(
|
||||||
let subject = match subject_builder.build_from_result(result) {
|
args.matcher()?,
|
||||||
Some(subject) => subject,
|
args.searcher()?,
|
||||||
None => continue,
|
args.printer(mode, args.stdout()),
|
||||||
};
|
)?;
|
||||||
|
for haystack in haystacks {
|
||||||
searched = true;
|
searched = true;
|
||||||
let search_result = match searcher.search(&subject) {
|
let search_result = match searcher.search(&haystack) {
|
||||||
Ok(search_result) => search_result,
|
Ok(search_result) => search_result,
|
||||||
|
// A broken pipe means graceful termination.
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => break,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// A broken pipe means graceful termination.
|
err_message!("{}: {}", haystack.path().display(), err);
|
||||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
err_message!("{}: {}", subject.path().display(), err);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -106,66 +136,66 @@ fn search(args: &Args) -> Result<bool> {
|
|||||||
if let Some(ref mut stats) = stats {
|
if let Some(ref mut stats) = stats {
|
||||||
*stats += search_result.stats().unwrap();
|
*stats += search_result.stats().unwrap();
|
||||||
}
|
}
|
||||||
if matched && quit_after_match {
|
if matched && args.quit_after_match() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if args.using_default_path() && !searched {
|
if args.has_implicit_path() && !searched {
|
||||||
eprint_nothing_searched();
|
eprint_nothing_searched();
|
||||||
}
|
}
|
||||||
if let Some(ref stats) = stats {
|
if let Some(ref stats) = stats {
|
||||||
let elapsed = Instant::now().duration_since(started_at);
|
let wtr = searcher.printer().get_mut();
|
||||||
// We don't care if we couldn't print this successfully.
|
let _ = print_stats(mode, stats, started_at, wtr);
|
||||||
let _ = searcher.print_stats(elapsed, stats);
|
|
||||||
}
|
}
|
||||||
Ok(matched)
|
Ok(matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The top-level entry point for multi-threaded search. The parallelism is
|
/// The top-level entry point for multi-threaded search.
|
||||||
/// itself achieved by the recursive directory traversal. All we need to do is
|
///
|
||||||
/// feed it a worker for performing a search on each file.
|
/// The parallelism is itself achieved by the recursive directory traversal.
|
||||||
fn search_parallel(args: &Args) -> Result<bool> {
|
/// All we need to do is feed it a worker for performing a search on each file.
|
||||||
use std::sync::atomic::AtomicBool;
|
///
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
/// Requesting a sorted output from ripgrep (such as with `--sort path`) will
|
||||||
|
/// automatically disable parallelism and hence sorting is not handled here.
|
||||||
|
fn search_parallel(args: &HiArgs, mode: SearchMode) -> anyhow::Result<bool> {
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
let quit_after_match = args.quit_after_match()?;
|
let started_at = std::time::Instant::now();
|
||||||
let started_at = Instant::now();
|
let haystack_builder = args.haystack_builder();
|
||||||
let subject_builder = args.subject_builder();
|
let bufwtr = args.buffer_writer();
|
||||||
let bufwtr = args.buffer_writer()?;
|
let stats = args.stats().map(std::sync::Mutex::new);
|
||||||
let stats = args.stats()?.map(Mutex::new);
|
|
||||||
let matched = AtomicBool::new(false);
|
let matched = AtomicBool::new(false);
|
||||||
let searched = AtomicBool::new(false);
|
let searched = AtomicBool::new(false);
|
||||||
let mut searcher_err = None;
|
|
||||||
args.walker_parallel()?.run(|| {
|
let mut searcher = args.search_worker(
|
||||||
|
args.matcher()?,
|
||||||
|
args.searcher()?,
|
||||||
|
args.printer(mode, bufwtr.buffer()),
|
||||||
|
)?;
|
||||||
|
args.walk_builder()?.build_parallel().run(|| {
|
||||||
let bufwtr = &bufwtr;
|
let bufwtr = &bufwtr;
|
||||||
let stats = &stats;
|
let stats = &stats;
|
||||||
let matched = &matched;
|
let matched = &matched;
|
||||||
let searched = &searched;
|
let searched = &searched;
|
||||||
let subject_builder = &subject_builder;
|
let haystack_builder = &haystack_builder;
|
||||||
let mut searcher = match args.search_worker(bufwtr.buffer()) {
|
let mut searcher = searcher.clone();
|
||||||
Ok(searcher) => searcher,
|
|
||||||
Err(err) => {
|
|
||||||
searcher_err = Some(err);
|
|
||||||
return Box::new(move |_| WalkState::Quit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::new(move |result| {
|
Box::new(move |result| {
|
||||||
let subject = match subject_builder.build_from_result(result) {
|
let haystack = match haystack_builder.build_from_result(result) {
|
||||||
Some(subject) => subject,
|
Some(haystack) => haystack,
|
||||||
None => return WalkState::Continue,
|
None => return WalkState::Continue,
|
||||||
};
|
};
|
||||||
searched.store(true, SeqCst);
|
searched.store(true, Ordering::SeqCst);
|
||||||
searcher.printer().get_mut().clear();
|
searcher.printer().get_mut().clear();
|
||||||
let search_result = match searcher.search(&subject) {
|
let search_result = match searcher.search(&haystack) {
|
||||||
Ok(search_result) => search_result,
|
Ok(search_result) => search_result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
err_message!("{}: {}", subject.path().display(), err);
|
err_message!("{}: {}", haystack.path().display(), err);
|
||||||
return WalkState::Continue;
|
return WalkState::Continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if search_result.has_match() {
|
if search_result.has_match() {
|
||||||
matched.store(true, SeqCst);
|
matched.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
if let Some(ref locked_stats) = *stats {
|
if let Some(ref locked_stats) = *stats {
|
||||||
let mut stats = locked_stats.lock().unwrap();
|
let mut stats = locked_stats.lock().unwrap();
|
||||||
@ -173,63 +203,53 @@ fn search_parallel(args: &Args) -> Result<bool> {
|
|||||||
}
|
}
|
||||||
if let Err(err) = bufwtr.print(searcher.printer().get_mut()) {
|
if let Err(err) = bufwtr.print(searcher.printer().get_mut()) {
|
||||||
// A broken pipe means graceful termination.
|
// A broken pipe means graceful termination.
|
||||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
if err.kind() == std::io::ErrorKind::BrokenPipe {
|
||||||
return WalkState::Quit;
|
return WalkState::Quit;
|
||||||
}
|
}
|
||||||
// Otherwise, we continue on our merry way.
|
// Otherwise, we continue on our merry way.
|
||||||
err_message!("{}: {}", subject.path().display(), err);
|
err_message!("{}: {}", haystack.path().display(), err);
|
||||||
}
|
}
|
||||||
if matched.load(SeqCst) && quit_after_match {
|
if matched.load(Ordering::SeqCst) && args.quit_after_match() {
|
||||||
WalkState::Quit
|
WalkState::Quit
|
||||||
} else {
|
} else {
|
||||||
WalkState::Continue
|
WalkState::Continue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
if let Some(err) = searcher_err.take() {
|
if args.has_implicit_path() && !searched.load(Ordering::SeqCst) {
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
if args.using_default_path() && !searched.load(SeqCst) {
|
|
||||||
eprint_nothing_searched();
|
eprint_nothing_searched();
|
||||||
}
|
}
|
||||||
if let Some(ref locked_stats) = stats {
|
if let Some(ref locked_stats) = stats {
|
||||||
let elapsed = Instant::now().duration_since(started_at);
|
|
||||||
let stats = locked_stats.lock().unwrap();
|
let stats = locked_stats.lock().unwrap();
|
||||||
let mut searcher = args.search_worker(args.stdout())?;
|
let mut wtr = searcher.printer().get_mut();
|
||||||
// We don't care if we couldn't print this successfully.
|
let _ = print_stats(mode, &stats, started_at, &mut wtr);
|
||||||
let _ = searcher.print_stats(elapsed, &stats);
|
let _ = bufwtr.print(&mut wtr);
|
||||||
}
|
}
|
||||||
Ok(matched.load(SeqCst))
|
Ok(matched.load(Ordering::SeqCst))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eprint_nothing_searched() {
|
/// The top-level entry point for file listing without searching.
|
||||||
err_message!(
|
///
|
||||||
"No files were searched, which means ripgrep probably \
|
/// This recursively steps through the file list (current directory by default)
|
||||||
applied a filter you didn't expect.\n\
|
/// and prints each path sequentially using a single thread.
|
||||||
Running with --debug will show why files are being skipped."
|
fn files(args: &HiArgs) -> anyhow::Result<bool> {
|
||||||
);
|
let haystack_builder = args.haystack_builder();
|
||||||
}
|
let unsorted = args
|
||||||
|
.walk_builder()?
|
||||||
|
.build()
|
||||||
|
.filter_map(|result| haystack_builder.build_from_result(result));
|
||||||
|
let haystacks = args.sort(unsorted);
|
||||||
|
|
||||||
/// The top-level entry point for listing files without searching them. This
|
|
||||||
/// recursively steps through the file list (current directory by default) and
|
|
||||||
/// prints each path sequentially using a single thread.
|
|
||||||
fn files(args: &Args) -> Result<bool> {
|
|
||||||
let quit_after_match = args.quit_after_match()?;
|
|
||||||
let subject_builder = args.subject_builder();
|
|
||||||
let mut matched = false;
|
let mut matched = false;
|
||||||
let mut path_printer = args.path_printer(args.stdout())?;
|
let mut path_printer = args.path_printer_builder().build(args.stdout());
|
||||||
for result in args.walker()? {
|
for haystack in haystacks {
|
||||||
let subject = match subject_builder.build_from_result(result) {
|
|
||||||
Some(subject) => subject,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
matched = true;
|
matched = true;
|
||||||
if quit_after_match {
|
if args.quit_after_match() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if let Err(err) = path_printer.write_path(subject.path()) {
|
if let Err(err) = path_printer.write(haystack.path()) {
|
||||||
// A broken pipe means graceful termination.
|
// A broken pipe means graceful termination.
|
||||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
if err.kind() == std::io::ErrorKind::BrokenPipe {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Otherwise, we have some other error that's preventing us from
|
// Otherwise, we have some other error that's preventing us from
|
||||||
@ -240,42 +260,53 @@ fn files(args: &Args) -> Result<bool> {
|
|||||||
Ok(matched)
|
Ok(matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The top-level entry point for listing files without searching them. This
|
/// The top-level entry point for multi-threaded file listing without
|
||||||
/// recursively steps through the file list (current directory by default) and
|
/// searching.
|
||||||
/// prints each path sequentially using multiple threads.
|
///
|
||||||
fn files_parallel(args: &Args) -> Result<bool> {
|
/// This recursively steps through the file list (current directory by default)
|
||||||
use std::sync::atomic::AtomicBool;
|
/// and prints each path sequentially using multiple threads.
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
///
|
||||||
use std::sync::mpsc;
|
/// Requesting a sorted output from ripgrep (such as with `--sort path`) will
|
||||||
use std::thread;
|
/// automatically disable parallelism and hence sorting is not handled here.
|
||||||
|
fn files_parallel(args: &HiArgs) -> anyhow::Result<bool> {
|
||||||
|
use std::{
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
mpsc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
let quit_after_match = args.quit_after_match()?;
|
let haystack_builder = args.haystack_builder();
|
||||||
let subject_builder = args.subject_builder();
|
let mut path_printer = args.path_printer_builder().build(args.stdout());
|
||||||
let mut path_printer = args.path_printer(args.stdout())?;
|
|
||||||
let matched = AtomicBool::new(false);
|
let matched = AtomicBool::new(false);
|
||||||
let (tx, rx) = mpsc::channel::<Subject>();
|
let (tx, rx) = mpsc::channel::<crate::haystack::Haystack>();
|
||||||
|
|
||||||
let print_thread = thread::spawn(move || -> io::Result<()> {
|
// We spawn a single printing thread to make sure we don't tear writes.
|
||||||
for subject in rx.iter() {
|
// We use a channel here under the presumption that it's probably faster
|
||||||
path_printer.write_path(subject.path())?;
|
// than using a mutex in the worker threads below, but this has never been
|
||||||
|
// seriously litigated.
|
||||||
|
let print_thread = thread::spawn(move || -> std::io::Result<()> {
|
||||||
|
for haystack in rx.iter() {
|
||||||
|
path_printer.write(haystack.path())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
args.walker_parallel()?.run(|| {
|
args.walk_builder()?.build_parallel().run(|| {
|
||||||
let subject_builder = &subject_builder;
|
let haystack_builder = &haystack_builder;
|
||||||
let matched = &matched;
|
let matched = &matched;
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
|
|
||||||
Box::new(move |result| {
|
Box::new(move |result| {
|
||||||
let subject = match subject_builder.build_from_result(result) {
|
let haystack = match haystack_builder.build_from_result(result) {
|
||||||
Some(subject) => subject,
|
Some(haystack) => haystack,
|
||||||
None => return WalkState::Continue,
|
None => return WalkState::Continue,
|
||||||
};
|
};
|
||||||
matched.store(true, SeqCst);
|
matched.store(true, Ordering::SeqCst);
|
||||||
if quit_after_match {
|
if args.quit_after_match() {
|
||||||
WalkState::Quit
|
WalkState::Quit
|
||||||
} else {
|
} else {
|
||||||
match tx.send(subject) {
|
match tx.send(haystack) {
|
||||||
Ok(_) => WalkState::Continue,
|
Ok(_) => WalkState::Continue,
|
||||||
Err(_) => WalkState::Quit,
|
Err(_) => WalkState::Quit,
|
||||||
}
|
}
|
||||||
@ -287,18 +318,18 @@ fn files_parallel(args: &Args) -> Result<bool> {
|
|||||||
// A broken pipe means graceful termination, so fall through.
|
// A broken pipe means graceful termination, so fall through.
|
||||||
// Otherwise, something bad happened while writing to stdout, so bubble
|
// Otherwise, something bad happened while writing to stdout, so bubble
|
||||||
// it up.
|
// it up.
|
||||||
if err.kind() != io::ErrorKind::BrokenPipe {
|
if err.kind() != std::io::ErrorKind::BrokenPipe {
|
||||||
return Err(err.into());
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(matched.load(SeqCst))
|
Ok(matched.load(Ordering::SeqCst))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The top-level entry point for --type-list.
|
/// The top-level entry point for `--type-list`.
|
||||||
fn types(args: &Args) -> Result<bool> {
|
fn types(args: &HiArgs) -> anyhow::Result<ExitCode> {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut stdout = args.stdout();
|
let mut stdout = args.stdout();
|
||||||
for def in args.type_defs()? {
|
for def in args.types().definitions() {
|
||||||
count += 1;
|
count += 1;
|
||||||
stdout.write_all(def.name().as_bytes())?;
|
stdout.write_all(def.name().as_bytes())?;
|
||||||
stdout.write_all(b": ")?;
|
stdout.write_all(b": ")?;
|
||||||
@ -313,32 +344,140 @@ fn types(args: &Args) -> Result<bool> {
|
|||||||
}
|
}
|
||||||
stdout.write_all(b"\n")?;
|
stdout.write_all(b"\n")?;
|
||||||
}
|
}
|
||||||
Ok(count > 0)
|
Ok(ExitCode::from(if count == 0 { 1 } else { 0 }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The top-level entry point for --pcre2-version.
|
/// Implements ripgrep's "generate" modes.
|
||||||
fn pcre2_version(args: &Args) -> Result<bool> {
|
///
|
||||||
#[cfg(feature = "pcre2")]
|
/// These modes correspond to generating some kind of ancillary data related
|
||||||
fn imp(args: &Args) -> Result<bool> {
|
/// to ripgrep. At present, this includes ripgrep's man page (in roff format)
|
||||||
use grep::pcre2;
|
/// and supported shell completions.
|
||||||
|
fn generate(mode: crate::flags::GenerateMode) -> anyhow::Result<ExitCode> {
|
||||||
|
use crate::flags::GenerateMode;
|
||||||
|
|
||||||
let mut stdout = args.stdout();
|
let output = match mode {
|
||||||
|
GenerateMode::Man => flags::generate_man_page(),
|
||||||
let (major, minor) = pcre2::version();
|
GenerateMode::CompleteBash => flags::generate_complete_bash(),
|
||||||
writeln!(stdout, "PCRE2 {}.{} is available", major, minor)?;
|
GenerateMode::CompleteZsh => flags::generate_complete_zsh(),
|
||||||
|
GenerateMode::CompleteFish => flags::generate_complete_fish(),
|
||||||
if cfg!(target_pointer_width = "64") && pcre2::is_jit_available() {
|
GenerateMode::CompletePowerShell => {
|
||||||
writeln!(stdout, "JIT is available")?;
|
flags::generate_complete_powershell()
|
||||||
}
|
}
|
||||||
Ok(true)
|
};
|
||||||
}
|
writeln!(std::io::stdout(), "{}", output.trim_end())?;
|
||||||
|
Ok(ExitCode::from(0))
|
||||||
#[cfg(not(feature = "pcre2"))]
|
}
|
||||||
fn imp(args: &Args) -> Result<bool> {
|
|
||||||
let mut stdout = args.stdout();
|
/// Implements ripgrep's "special" modes.
|
||||||
writeln!(stdout, "PCRE2 is not available in this build of ripgrep.")?;
|
///
|
||||||
Ok(false)
|
/// A special mode is one that generally short-circuits most (not all) of
|
||||||
}
|
/// ripgrep's initialization logic and skips right to this routine. The
|
||||||
|
/// special modes essentially consist of printing help and version output. The
|
||||||
imp(args)
|
/// idea behind the short circuiting is to ensure there is as little as possible
|
||||||
|
/// (within reason) that would prevent ripgrep from emitting help output.
|
||||||
|
///
|
||||||
|
/// For example, part of the initialization logic that is skipped (among
|
||||||
|
/// other things) is accessing the current working directory. If that fails,
|
||||||
|
/// ripgrep emits an error. We don't want to emit an error if it fails and
|
||||||
|
/// the user requested version or help information.
|
||||||
|
fn special(mode: crate::flags::SpecialMode) -> anyhow::Result<ExitCode> {
|
||||||
|
use crate::flags::SpecialMode;
|
||||||
|
|
||||||
|
let mut exit = ExitCode::from(0);
|
||||||
|
let output = match mode {
|
||||||
|
SpecialMode::HelpShort => flags::generate_help_short(),
|
||||||
|
SpecialMode::HelpLong => flags::generate_help_long(),
|
||||||
|
SpecialMode::VersionShort => flags::generate_version_short(),
|
||||||
|
SpecialMode::VersionLong => flags::generate_version_long(),
|
||||||
|
// --pcre2-version is a little special because it emits an error
|
||||||
|
// exit code if this build of ripgrep doesn't support PCRE2.
|
||||||
|
SpecialMode::VersionPCRE2 => {
|
||||||
|
let (output, available) = flags::generate_version_pcre2();
|
||||||
|
if !available {
|
||||||
|
exit = ExitCode::from(1);
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeln!(std::io::stdout(), "{}", output.trim_end())?;
|
||||||
|
Ok(exit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a heuristic error messages when nothing is searched.
|
||||||
|
///
|
||||||
|
/// This can happen if an applicable ignore file has one or more rules that
|
||||||
|
/// are too broad and cause ripgrep to ignore everything.
|
||||||
|
///
|
||||||
|
/// We only show this error message when the user does *not* provide an
|
||||||
|
/// explicit path to search. This is because the message can otherwise be
|
||||||
|
/// noisy, e.g., when it is intended that there is nothing to search.
|
||||||
|
fn eprint_nothing_searched() {
|
||||||
|
err_message!(
|
||||||
|
"No files were searched, which means ripgrep probably \
|
||||||
|
applied a filter you didn't expect.\n\
|
||||||
|
Running with --debug will show why files are being skipped."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints the statistics given to the writer given.
|
||||||
|
///
|
||||||
|
/// The search mode given determines whether the stats should be printed in
|
||||||
|
/// a plain text format or in a JSON format.
|
||||||
|
///
|
||||||
|
/// The `started` time should be the time at which ripgrep started working.
|
||||||
|
///
|
||||||
|
/// If an error occurs while writing, then writing stops and the error is
|
||||||
|
/// returned. Note that callers should probably ignore this errror, since
|
||||||
|
/// whether stats fail to print or not generally shouldn't cause ripgrep to
|
||||||
|
/// enter into an "error" state. And usually the only way for this to fail is
|
||||||
|
/// if writing to stdout itself fails.
|
||||||
|
fn print_stats<W: Write>(
|
||||||
|
mode: SearchMode,
|
||||||
|
stats: &grep::printer::Stats,
|
||||||
|
started: std::time::Instant,
|
||||||
|
mut wtr: W,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let elapsed = std::time::Instant::now().duration_since(started);
|
||||||
|
if matches!(mode, SearchMode::JSON) {
|
||||||
|
// We specifically match the format laid out by the JSON printer in
|
||||||
|
// the grep-printer crate. We simply "extend" it with the 'summary'
|
||||||
|
// message type.
|
||||||
|
serde_json::to_writer(
|
||||||
|
&mut wtr,
|
||||||
|
&serde_json::json!({
|
||||||
|
"type": "summary",
|
||||||
|
"data": {
|
||||||
|
"stats": stats,
|
||||||
|
"elapsed_total": {
|
||||||
|
"secs": elapsed.as_secs(),
|
||||||
|
"nanos": elapsed.subsec_nanos(),
|
||||||
|
"human": format!("{:0.6}s", elapsed.as_secs_f64()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
write!(wtr, "\n")
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
wtr,
|
||||||
|
"
|
||||||
|
{matches} matches
|
||||||
|
{lines} matched lines
|
||||||
|
{searches_with_match} files contained matches
|
||||||
|
{searches} files searched
|
||||||
|
{bytes_printed} bytes printed
|
||||||
|
{bytes_searched} bytes searched
|
||||||
|
{search_time:0.6} seconds spent searching
|
||||||
|
{process_time:0.6} seconds
|
||||||
|
",
|
||||||
|
matches = stats.matches(),
|
||||||
|
lines = stats.matched_lines(),
|
||||||
|
searches_with_match = stats.searches_with_match(),
|
||||||
|
searches = stats.searches(),
|
||||||
|
bytes_printed = stats.bytes_printed(),
|
||||||
|
bytes_searched = stats.bytes_searched(),
|
||||||
|
search_time = stats.elapsed().as_secs_f64(),
|
||||||
|
process_time = elapsed.as_secs_f64(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,77 @@
|
|||||||
|
/*!
|
||||||
|
This module defines some macros and some light shared mutable state.
|
||||||
|
|
||||||
|
This state is responsible for keeping track of whether we should emit certain
|
||||||
|
kinds of messages to the user (such as errors) that are distinct from the
|
||||||
|
standard "debug" or "trace" log messages. This state is specifically set at
|
||||||
|
startup time when CLI arguments are parsed and then never changed.
|
||||||
|
|
||||||
|
The other state tracked here is whether ripgrep experienced an error
|
||||||
|
condition. Aside from errors associated with invalid CLI arguments, ripgrep
|
||||||
|
generally does not abort when an error occurs (e.g., if reading a file failed).
|
||||||
|
But when an error does occur, it will alter ripgrep's exit status. Thus, when
|
||||||
|
an error message is emitted via `err_message`, then a global flag is toggled
|
||||||
|
indicating that at least one error occurred. When ripgrep exits, this flag is
|
||||||
|
consulted to determine what the exit status ought to be.
|
||||||
|
*/
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
/// When false, "messages" will not be printed.
|
||||||
static MESSAGES: AtomicBool = AtomicBool::new(false);
|
static MESSAGES: AtomicBool = AtomicBool::new(false);
|
||||||
|
/// When false, "messages" related to ignore rules will not be printed.
|
||||||
static IGNORE_MESSAGES: AtomicBool = AtomicBool::new(false);
|
static IGNORE_MESSAGES: AtomicBool = AtomicBool::new(false);
|
||||||
|
/// Flipped to true when an error message is printed.
|
||||||
static ERRORED: AtomicBool = AtomicBool::new(false);
|
static ERRORED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// Like eprintln, but locks stdout to prevent interleaving lines.
|
||||||
|
///
|
||||||
|
/// This locks stdout, not stderr, even though this prints to stderr. This
|
||||||
|
/// avoids the appearance of interleaving output when stdout and stderr both
|
||||||
|
/// correspond to a tty.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! eprintln_locked {
|
||||||
|
($($tt:tt)*) => {{
|
||||||
|
{
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
// This is a bit of an abstraction violation because we explicitly
|
||||||
|
// lock stdout before printing to stderr. This avoids interleaving
|
||||||
|
// lines within ripgrep because `search_parallel` uses `termcolor`,
|
||||||
|
// which accesses the same stdout lock when writing lines.
|
||||||
|
let stdout = std::io::stdout().lock();
|
||||||
|
let mut stderr = std::io::stderr().lock();
|
||||||
|
// We specifically ignore any errors here. One plausible error we
|
||||||
|
// can get in some cases is a broken pipe error. And when that
|
||||||
|
// occurs, we should exit gracefully. Otherwise, just abort with
|
||||||
|
// an error code because there isn't much else we can do.
|
||||||
|
//
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1966
|
||||||
|
if let Err(err) = write!(stderr, "rg: ") {
|
||||||
|
if err.kind() == std::io::ErrorKind::BrokenPipe {
|
||||||
|
std::process::exit(0);
|
||||||
|
} else {
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(err) = writeln!(stderr, $($tt)*) {
|
||||||
|
if err.kind() == std::io::ErrorKind::BrokenPipe {
|
||||||
|
std::process::exit(0);
|
||||||
|
} else {
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(stdout);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
/// Emit a non-fatal error message, unless messages were disabled.
|
/// Emit a non-fatal error message, unless messages were disabled.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! message {
|
macro_rules! message {
|
||||||
($($tt:tt)*) => {
|
($($tt:tt)*) => {
|
||||||
if crate::messages::messages() {
|
if crate::messages::messages() {
|
||||||
eprintln!($($tt)*);
|
eprintln_locked!($($tt)*);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,25 +92,25 @@ macro_rules! err_message {
|
|||||||
macro_rules! ignore_message {
|
macro_rules! ignore_message {
|
||||||
($($tt:tt)*) => {
|
($($tt:tt)*) => {
|
||||||
if crate::messages::messages() && crate::messages::ignore_messages() {
|
if crate::messages::messages() && crate::messages::ignore_messages() {
|
||||||
eprintln!($($tt)*);
|
eprintln_locked!($($tt)*);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if messages should be shown.
|
/// Returns true if and only if messages should be shown.
|
||||||
pub fn messages() -> bool {
|
pub(crate) fn messages() -> bool {
|
||||||
MESSAGES.load(Ordering::SeqCst)
|
MESSAGES.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set whether messages should be shown or not.
|
/// Set whether messages should be shown or not.
|
||||||
///
|
///
|
||||||
/// By default, they are not shown.
|
/// By default, they are not shown.
|
||||||
pub fn set_messages(yes: bool) {
|
pub(crate) fn set_messages(yes: bool) {
|
||||||
MESSAGES.store(yes, Ordering::SeqCst)
|
MESSAGES.store(yes, Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if "ignore" related messages should be shown.
|
/// Returns true if and only if "ignore" related messages should be shown.
|
||||||
pub fn ignore_messages() -> bool {
|
pub(crate) fn ignore_messages() -> bool {
|
||||||
IGNORE_MESSAGES.load(Ordering::SeqCst)
|
IGNORE_MESSAGES.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,16 +121,19 @@ pub fn ignore_messages() -> bool {
|
|||||||
/// Note that this is overridden if `messages` is disabled. Namely, if
|
/// Note that this is overridden if `messages` is disabled. Namely, if
|
||||||
/// `messages` is disabled, then "ignore" messages are never shown, regardless
|
/// `messages` is disabled, then "ignore" messages are never shown, regardless
|
||||||
/// of this setting.
|
/// of this setting.
|
||||||
pub fn set_ignore_messages(yes: bool) {
|
pub(crate) fn set_ignore_messages(yes: bool) {
|
||||||
IGNORE_MESSAGES.store(yes, Ordering::SeqCst)
|
IGNORE_MESSAGES.store(yes, Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if ripgrep came across a non-fatal error.
|
/// Returns true if and only if ripgrep came across a non-fatal error.
|
||||||
pub fn errored() -> bool {
|
pub(crate) fn errored() -> bool {
|
||||||
ERRORED.load(Ordering::SeqCst)
|
ERRORED.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicate that ripgrep has come across a non-fatal error.
|
/// Indicate that ripgrep has come across a non-fatal error.
|
||||||
pub fn set_errored() {
|
///
|
||||||
|
/// Callers should not use this directly. Instead, it is called automatically
|
||||||
|
/// via the `err_message` macro.
|
||||||
|
pub(crate) fn set_errored() {
|
||||||
ERRORED.store(true, Ordering::SeqCst);
|
ERRORED.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use grep::printer::{ColorSpecs, PrinterPath};
|
|
||||||
use termcolor::WriteColor;
|
|
||||||
|
|
||||||
/// A configuration for describing how paths should be written.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Config {
|
|
||||||
colors: ColorSpecs,
|
|
||||||
separator: Option<u8>,
|
|
||||||
terminator: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Config {
|
|
||||||
Config {
|
|
||||||
colors: ColorSpecs::default(),
|
|
||||||
separator: None,
|
|
||||||
terminator: b'\n',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A builder for constructing things to search over.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct PathPrinterBuilder {
|
|
||||||
config: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathPrinterBuilder {
|
|
||||||
/// Return a new subject builder with a default configuration.
|
|
||||||
pub fn new() -> PathPrinterBuilder {
|
|
||||||
PathPrinterBuilder { config: Config::default() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new path printer with the current configuration that writes
|
|
||||||
/// paths to the given writer.
|
|
||||||
pub fn build<W: WriteColor>(&self, wtr: W) -> PathPrinter<W> {
|
|
||||||
PathPrinter { config: self.config.clone(), wtr }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the color specification for this printer.
|
|
||||||
///
|
|
||||||
/// Currently, only the `path` component of the given specification is
|
|
||||||
/// used.
|
|
||||||
pub fn color_specs(
|
|
||||||
&mut self,
|
|
||||||
specs: ColorSpecs,
|
|
||||||
) -> &mut PathPrinterBuilder {
|
|
||||||
self.config.colors = specs;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A path separator.
|
|
||||||
///
|
|
||||||
/// When provided, the path's default separator will be replaced with
|
|
||||||
/// the given separator.
|
|
||||||
///
|
|
||||||
/// This is not set by default, and the system's default path separator
|
|
||||||
/// will be used.
|
|
||||||
pub fn separator(&mut self, sep: Option<u8>) -> &mut PathPrinterBuilder {
|
|
||||||
self.config.separator = sep;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A path terminator.
|
|
||||||
///
|
|
||||||
/// When printing a path, it will be by terminated by the given byte.
|
|
||||||
///
|
|
||||||
/// This is set to `\n` by default.
|
|
||||||
pub fn terminator(&mut self, terminator: u8) -> &mut PathPrinterBuilder {
|
|
||||||
self.config.terminator = terminator;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A printer for emitting paths to a writer, with optional color support.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PathPrinter<W> {
|
|
||||||
config: Config,
|
|
||||||
wtr: W,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: WriteColor> PathPrinter<W> {
|
|
||||||
/// Write the given path to the underlying writer.
|
|
||||||
pub fn write_path(&mut self, path: &Path) -> io::Result<()> {
|
|
||||||
let ppath = PrinterPath::with_separator(path, self.config.separator);
|
|
||||||
if !self.wtr.supports_color() {
|
|
||||||
self.wtr.write_all(ppath.as_bytes())?;
|
|
||||||
} else {
|
|
||||||
self.wtr.set_color(self.config.colors.path())?;
|
|
||||||
self.wtr.write_all(ppath.as_bytes())?;
|
|
||||||
self.wtr.reset()?;
|
|
||||||
}
|
|
||||||
self.wtr.write_all(&[self.config.terminator])
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +1,47 @@
|
|||||||
use std::fs::File;
|
/*!
|
||||||
use std::io;
|
Defines a very high level "search worker" abstraction.
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use grep::cli;
|
A search worker manages the high level interaction points between the matcher
|
||||||
use grep::matcher::Matcher;
|
(i.e., which regex engine is used), the searcher (i.e., how data is actually
|
||||||
#[cfg(feature = "pcre2")]
|
read and matched using the regex engine) and the printer. For example, the
|
||||||
use grep::pcre2::RegexMatcher as PCRE2RegexMatcher;
|
search worker is where things like preprocessors or decompression happens.
|
||||||
use grep::printer::{Standard, Stats, Summary, JSON};
|
*/
|
||||||
use grep::regex::RegexMatcher as RustRegexMatcher;
|
|
||||||
use grep::searcher::{BinaryDetection, Searcher};
|
|
||||||
use ignore::overrides::Override;
|
|
||||||
use serde_json as json;
|
|
||||||
use serde_json::json;
|
|
||||||
use termcolor::WriteColor;
|
|
||||||
|
|
||||||
use crate::subject::Subject;
|
use std::{io, path::Path};
|
||||||
|
|
||||||
/// The configuration for the search worker. Among a few other things, the
|
use {grep::matcher::Matcher, termcolor::WriteColor};
|
||||||
/// configuration primarily controls the way we show search results to users
|
|
||||||
/// at a very high level.
|
/// The configuration for the search worker.
|
||||||
|
///
|
||||||
|
/// Among a few other things, the configuration primarily controls the way we
|
||||||
|
/// show search results to users at a very high level.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Config {
|
struct Config {
|
||||||
json_stats: bool,
|
preprocessor: Option<std::path::PathBuf>,
|
||||||
preprocessor: Option<PathBuf>,
|
preprocessor_globs: ignore::overrides::Override,
|
||||||
preprocessor_globs: Override,
|
|
||||||
search_zip: bool,
|
search_zip: bool,
|
||||||
binary_implicit: BinaryDetection,
|
binary_implicit: grep::searcher::BinaryDetection,
|
||||||
binary_explicit: BinaryDetection,
|
binary_explicit: grep::searcher::BinaryDetection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
Config {
|
Config {
|
||||||
json_stats: false,
|
|
||||||
preprocessor: None,
|
preprocessor: None,
|
||||||
preprocessor_globs: Override::empty(),
|
preprocessor_globs: ignore::overrides::Override::empty(),
|
||||||
search_zip: false,
|
search_zip: false,
|
||||||
binary_implicit: BinaryDetection::none(),
|
binary_implicit: grep::searcher::BinaryDetection::none(),
|
||||||
binary_explicit: BinaryDetection::none(),
|
binary_explicit: grep::searcher::BinaryDetection::none(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for configuring and constructing a search worker.
|
/// A builder for configuring and constructing a search worker.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SearchWorkerBuilder {
|
pub(crate) struct SearchWorkerBuilder {
|
||||||
config: Config,
|
config: Config,
|
||||||
command_builder: cli::CommandReaderBuilder,
|
command_builder: grep::cli::CommandReaderBuilder,
|
||||||
decomp_builder: cli::DecompressionReaderBuilder,
|
decomp_builder: grep::cli::DecompressionReaderBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SearchWorkerBuilder {
|
impl Default for SearchWorkerBuilder {
|
||||||
@ -60,11 +52,11 @@ impl Default for SearchWorkerBuilder {
|
|||||||
|
|
||||||
impl SearchWorkerBuilder {
|
impl SearchWorkerBuilder {
|
||||||
/// Create a new builder for configuring and constructing a search worker.
|
/// Create a new builder for configuring and constructing a search worker.
|
||||||
pub fn new() -> SearchWorkerBuilder {
|
pub(crate) fn new() -> SearchWorkerBuilder {
|
||||||
let mut cmd_builder = cli::CommandReaderBuilder::new();
|
let mut cmd_builder = grep::cli::CommandReaderBuilder::new();
|
||||||
cmd_builder.async_stderr(true);
|
cmd_builder.async_stderr(true);
|
||||||
|
|
||||||
let mut decomp_builder = cli::DecompressionReaderBuilder::new();
|
let mut decomp_builder = grep::cli::DecompressionReaderBuilder::new();
|
||||||
decomp_builder.async_stderr(true);
|
decomp_builder.async_stderr(true);
|
||||||
|
|
||||||
SearchWorkerBuilder {
|
SearchWorkerBuilder {
|
||||||
@ -76,10 +68,10 @@ impl SearchWorkerBuilder {
|
|||||||
|
|
||||||
/// Create a new search worker using the given searcher, matcher and
|
/// Create a new search worker using the given searcher, matcher and
|
||||||
/// printer.
|
/// printer.
|
||||||
pub fn build<W: WriteColor>(
|
pub(crate) fn build<W: WriteColor>(
|
||||||
&self,
|
&self,
|
||||||
matcher: PatternMatcher,
|
matcher: PatternMatcher,
|
||||||
searcher: Searcher,
|
searcher: grep::searcher::Searcher,
|
||||||
printer: Printer<W>,
|
printer: Printer<W>,
|
||||||
) -> SearchWorker<W> {
|
) -> SearchWorker<W> {
|
||||||
let config = self.config.clone();
|
let config = self.config.clone();
|
||||||
@ -95,29 +87,17 @@ impl SearchWorkerBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Forcefully use JSON to emit statistics, even if the underlying printer
|
|
||||||
/// is not the JSON printer.
|
|
||||||
///
|
|
||||||
/// This is useful for implementing flag combinations like
|
|
||||||
/// `--json --quiet`, which uses the summary printer for implementing
|
|
||||||
/// `--quiet` but still wants to emit summary statistics, which should
|
|
||||||
/// be JSON formatted because of the `--json` flag.
|
|
||||||
pub fn json_stats(&mut self, yes: bool) -> &mut SearchWorkerBuilder {
|
|
||||||
self.config.json_stats = yes;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the path to a preprocessor command.
|
/// Set the path to a preprocessor command.
|
||||||
///
|
///
|
||||||
/// When this is set, instead of searching files directly, the given
|
/// When this is set, instead of searching files directly, the given
|
||||||
/// command will be run with the file path as the first argument, and the
|
/// command will be run with the file path as the first argument, and the
|
||||||
/// output of that command will be searched instead.
|
/// output of that command will be searched instead.
|
||||||
pub fn preprocessor(
|
pub(crate) fn preprocessor(
|
||||||
&mut self,
|
&mut self,
|
||||||
cmd: Option<PathBuf>,
|
cmd: Option<std::path::PathBuf>,
|
||||||
) -> crate::Result<&mut SearchWorkerBuilder> {
|
) -> anyhow::Result<&mut SearchWorkerBuilder> {
|
||||||
if let Some(ref prog) = cmd {
|
if let Some(ref prog) = cmd {
|
||||||
let bin = cli::resolve_binary(prog)?;
|
let bin = grep::cli::resolve_binary(prog)?;
|
||||||
self.config.preprocessor = Some(bin);
|
self.config.preprocessor = Some(bin);
|
||||||
} else {
|
} else {
|
||||||
self.config.preprocessor = None;
|
self.config.preprocessor = None;
|
||||||
@ -128,9 +108,9 @@ impl SearchWorkerBuilder {
|
|||||||
/// Set the globs for determining which files should be run through the
|
/// Set the globs for determining which files should be run through the
|
||||||
/// preprocessor. By default, with no globs and a preprocessor specified,
|
/// preprocessor. By default, with no globs and a preprocessor specified,
|
||||||
/// every file is run through the preprocessor.
|
/// every file is run through the preprocessor.
|
||||||
pub fn preprocessor_globs(
|
pub(crate) fn preprocessor_globs(
|
||||||
&mut self,
|
&mut self,
|
||||||
globs: Override,
|
globs: ignore::overrides::Override,
|
||||||
) -> &mut SearchWorkerBuilder {
|
) -> &mut SearchWorkerBuilder {
|
||||||
self.config.preprocessor_globs = globs;
|
self.config.preprocessor_globs = globs;
|
||||||
self
|
self
|
||||||
@ -143,7 +123,10 @@ impl SearchWorkerBuilder {
|
|||||||
///
|
///
|
||||||
/// Note that if a preprocessor command is set, then it overrides this
|
/// Note that if a preprocessor command is set, then it overrides this
|
||||||
/// setting.
|
/// setting.
|
||||||
pub fn search_zip(&mut self, yes: bool) -> &mut SearchWorkerBuilder {
|
pub(crate) fn search_zip(
|
||||||
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
|
) -> &mut SearchWorkerBuilder {
|
||||||
self.config.search_zip = yes;
|
self.config.search_zip = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -151,13 +134,14 @@ impl SearchWorkerBuilder {
|
|||||||
/// Set the binary detection that should be used when searching files
|
/// Set the binary detection that should be used when searching files
|
||||||
/// found via a recursive directory search.
|
/// found via a recursive directory search.
|
||||||
///
|
///
|
||||||
/// Generally, this binary detection may be `BinaryDetection::quit` if
|
/// Generally, this binary detection may be
|
||||||
/// we want to skip binary files completely.
|
/// `grep::searcher::BinaryDetection::quit` if we want to skip binary files
|
||||||
|
/// completely.
|
||||||
///
|
///
|
||||||
/// By default, no binary detection is performed.
|
/// By default, no binary detection is performed.
|
||||||
pub fn binary_detection_implicit(
|
pub(crate) fn binary_detection_implicit(
|
||||||
&mut self,
|
&mut self,
|
||||||
detection: BinaryDetection,
|
detection: grep::searcher::BinaryDetection,
|
||||||
) -> &mut SearchWorkerBuilder {
|
) -> &mut SearchWorkerBuilder {
|
||||||
self.config.binary_implicit = detection;
|
self.config.binary_implicit = detection;
|
||||||
self
|
self
|
||||||
@ -166,14 +150,14 @@ impl SearchWorkerBuilder {
|
|||||||
/// Set the binary detection that should be used when searching files
|
/// Set the binary detection that should be used when searching files
|
||||||
/// explicitly supplied by an end user.
|
/// explicitly supplied by an end user.
|
||||||
///
|
///
|
||||||
/// Generally, this binary detection should NOT be `BinaryDetection::quit`,
|
/// Generally, this binary detection should NOT be
|
||||||
/// since we never want to automatically filter files supplied by the end
|
/// `grep::searcher::BinaryDetection::quit`, since we never want to
|
||||||
/// user.
|
/// automatically filter files supplied by the end user.
|
||||||
///
|
///
|
||||||
/// By default, no binary detection is performed.
|
/// By default, no binary detection is performed.
|
||||||
pub fn binary_detection_explicit(
|
pub(crate) fn binary_detection_explicit(
|
||||||
&mut self,
|
&mut self,
|
||||||
detection: BinaryDetection,
|
detection: grep::searcher::BinaryDetection,
|
||||||
) -> &mut SearchWorkerBuilder {
|
) -> &mut SearchWorkerBuilder {
|
||||||
self.config.binary_explicit = detection;
|
self.config.binary_explicit = detection;
|
||||||
self
|
self
|
||||||
@ -187,14 +171,14 @@ impl SearchWorkerBuilder {
|
|||||||
/// every search also has some aggregate statistics or meta data that may be
|
/// every search also has some aggregate statistics or meta data that may be
|
||||||
/// useful to higher level routines.
|
/// useful to higher level routines.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct SearchResult {
|
pub(crate) struct SearchResult {
|
||||||
has_match: bool,
|
has_match: bool,
|
||||||
stats: Option<Stats>,
|
stats: Option<grep::printer::Stats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchResult {
|
impl SearchResult {
|
||||||
/// Whether the search found a match or not.
|
/// Whether the search found a match or not.
|
||||||
pub fn has_match(&self) -> bool {
|
pub(crate) fn has_match(&self) -> bool {
|
||||||
self.has_match
|
self.has_match
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,103 +186,36 @@ impl SearchResult {
|
|||||||
///
|
///
|
||||||
/// It can be expensive to compute statistics, so these are only present
|
/// It can be expensive to compute statistics, so these are only present
|
||||||
/// if explicitly enabled in the printer provided by the caller.
|
/// if explicitly enabled in the printer provided by the caller.
|
||||||
pub fn stats(&self) -> Option<&Stats> {
|
pub(crate) fn stats(&self) -> Option<&grep::printer::Stats> {
|
||||||
self.stats.as_ref()
|
self.stats.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pattern matcher used by a search worker.
|
/// The pattern matcher used by a search worker.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PatternMatcher {
|
pub(crate) enum PatternMatcher {
|
||||||
RustRegex(RustRegexMatcher),
|
RustRegex(grep::regex::RegexMatcher),
|
||||||
#[cfg(feature = "pcre2")]
|
#[cfg(feature = "pcre2")]
|
||||||
PCRE2(PCRE2RegexMatcher),
|
PCRE2(grep::pcre2::RegexMatcher),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The printer used by a search worker.
|
/// The printer used by a search worker.
|
||||||
///
|
///
|
||||||
/// The `W` type parameter refers to the type of the underlying writer.
|
/// The `W` type parameter refers to the type of the underlying writer.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Printer<W> {
|
pub(crate) enum Printer<W> {
|
||||||
/// Use the standard printer, which supports the classic grep-like format.
|
/// Use the standard printer, which supports the classic grep-like format.
|
||||||
Standard(Standard<W>),
|
Standard(grep::printer::Standard<W>),
|
||||||
/// Use the summary printer, which supports aggregate displays of search
|
/// Use the summary printer, which supports aggregate displays of search
|
||||||
/// results.
|
/// results.
|
||||||
Summary(Summary<W>),
|
Summary(grep::printer::Summary<W>),
|
||||||
/// A JSON printer, which emits results in the JSON Lines format.
|
/// A JSON printer, which emits results in the JSON Lines format.
|
||||||
JSON(JSON<W>),
|
JSON(grep::printer::JSON<W>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: WriteColor> Printer<W> {
|
impl<W: WriteColor> Printer<W> {
|
||||||
fn print_stats(
|
|
||||||
&mut self,
|
|
||||||
total_duration: Duration,
|
|
||||||
stats: &Stats,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
match *self {
|
|
||||||
Printer::JSON(_) => self.print_stats_json(total_duration, stats),
|
|
||||||
Printer::Standard(_) | Printer::Summary(_) => {
|
|
||||||
self.print_stats_human(total_duration, stats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_stats_human(
|
|
||||||
&mut self,
|
|
||||||
total_duration: Duration,
|
|
||||||
stats: &Stats,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
write!(
|
|
||||||
self.get_mut(),
|
|
||||||
"
|
|
||||||
{matches} matches
|
|
||||||
{lines} matched lines
|
|
||||||
{searches_with_match} files contained matches
|
|
||||||
{searches} files searched
|
|
||||||
{bytes_printed} bytes printed
|
|
||||||
{bytes_searched} bytes searched
|
|
||||||
{search_time:0.6} seconds spent searching
|
|
||||||
{process_time:0.6} seconds
|
|
||||||
",
|
|
||||||
matches = stats.matches(),
|
|
||||||
lines = stats.matched_lines(),
|
|
||||||
searches_with_match = stats.searches_with_match(),
|
|
||||||
searches = stats.searches(),
|
|
||||||
bytes_printed = stats.bytes_printed(),
|
|
||||||
bytes_searched = stats.bytes_searched(),
|
|
||||||
search_time = fractional_seconds(stats.elapsed()),
|
|
||||||
process_time = fractional_seconds(total_duration)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_stats_json(
|
|
||||||
&mut self,
|
|
||||||
total_duration: Duration,
|
|
||||||
stats: &Stats,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
// We specifically match the format laid out by the JSON printer in
|
|
||||||
// the grep-printer crate. We simply "extend" it with the 'summary'
|
|
||||||
// message type.
|
|
||||||
let fractional = fractional_seconds(total_duration);
|
|
||||||
json::to_writer(
|
|
||||||
self.get_mut(),
|
|
||||||
&json!({
|
|
||||||
"type": "summary",
|
|
||||||
"data": {
|
|
||||||
"stats": stats,
|
|
||||||
"elapsed_total": {
|
|
||||||
"secs": total_duration.as_secs(),
|
|
||||||
"nanos": total_duration.subsec_nanos(),
|
|
||||||
"human": format!("{:0.6}s", fractional),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
write!(self.get_mut(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a mutable reference to the underlying printer's writer.
|
/// Return a mutable reference to the underlying printer's writer.
|
||||||
pub fn get_mut(&mut self) -> &mut W {
|
pub(crate) fn get_mut(&mut self) -> &mut W {
|
||||||
match *self {
|
match *self {
|
||||||
Printer::Standard(ref mut p) => p.get_mut(),
|
Printer::Standard(ref mut p) => p.get_mut(),
|
||||||
Printer::Summary(ref mut p) => p.get_mut(),
|
Printer::Summary(ref mut p) => p.get_mut(),
|
||||||
@ -312,29 +229,32 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
/// It is intended for a single worker to execute many searches, and is
|
/// It is intended for a single worker to execute many searches, and is
|
||||||
/// generally intended to be used from a single thread. When searching using
|
/// generally intended to be used from a single thread. When searching using
|
||||||
/// multiple threads, it is better to create a new worker for each thread.
|
/// multiple threads, it is better to create a new worker for each thread.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SearchWorker<W> {
|
pub(crate) struct SearchWorker<W> {
|
||||||
config: Config,
|
config: Config,
|
||||||
command_builder: cli::CommandReaderBuilder,
|
command_builder: grep::cli::CommandReaderBuilder,
|
||||||
decomp_builder: cli::DecompressionReaderBuilder,
|
decomp_builder: grep::cli::DecompressionReaderBuilder,
|
||||||
matcher: PatternMatcher,
|
matcher: PatternMatcher,
|
||||||
searcher: Searcher,
|
searcher: grep::searcher::Searcher,
|
||||||
printer: Printer<W>,
|
printer: Printer<W>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: WriteColor> SearchWorker<W> {
|
impl<W: WriteColor> SearchWorker<W> {
|
||||||
/// Execute a search over the given subject.
|
/// Execute a search over the given haystack.
|
||||||
pub fn search(&mut self, subject: &Subject) -> io::Result<SearchResult> {
|
pub(crate) fn search(
|
||||||
let bin = if subject.is_explicit() {
|
&mut self,
|
||||||
|
haystack: &crate::haystack::Haystack,
|
||||||
|
) -> io::Result<SearchResult> {
|
||||||
|
let bin = if haystack.is_explicit() {
|
||||||
self.config.binary_explicit.clone()
|
self.config.binary_explicit.clone()
|
||||||
} else {
|
} else {
|
||||||
self.config.binary_implicit.clone()
|
self.config.binary_implicit.clone()
|
||||||
};
|
};
|
||||||
let path = subject.path();
|
let path = haystack.path();
|
||||||
log::trace!("{}: binary detection: {:?}", path.display(), bin);
|
log::trace!("{}: binary detection: {:?}", path.display(), bin);
|
||||||
|
|
||||||
self.searcher.set_binary_detection(bin);
|
self.searcher.set_binary_detection(bin);
|
||||||
if subject.is_stdin() {
|
if haystack.is_stdin() {
|
||||||
self.search_reader(path, &mut io::stdin().lock())
|
self.search_reader(path, &mut io::stdin().lock())
|
||||||
} else if self.should_preprocess(path) {
|
} else if self.should_preprocess(path) {
|
||||||
self.search_preprocessor(path)
|
self.search_preprocessor(path)
|
||||||
@ -346,28 +266,10 @@ impl<W: WriteColor> SearchWorker<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to the underlying printer.
|
/// Return a mutable reference to the underlying printer.
|
||||||
pub fn printer(&mut self) -> &mut Printer<W> {
|
pub(crate) fn printer(&mut self) -> &mut Printer<W> {
|
||||||
&mut self.printer
|
&mut self.printer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print the given statistics to the underlying writer in a way that is
|
|
||||||
/// consistent with this searcher's printer's format.
|
|
||||||
///
|
|
||||||
/// While `Stats` contains a duration itself, this only corresponds to the
|
|
||||||
/// time spent searching, where as `total_duration` should roughly
|
|
||||||
/// approximate the lifespan of the ripgrep process itself.
|
|
||||||
pub fn print_stats(
|
|
||||||
&mut self,
|
|
||||||
total_duration: Duration,
|
|
||||||
stats: &Stats,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
if self.config.json_stats {
|
|
||||||
self.printer().print_stats_json(total_duration, stats)
|
|
||||||
} else {
|
|
||||||
self.printer().print_stats(total_duration, stats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if and only if the given file path should be
|
/// Returns true if and only if the given file path should be
|
||||||
/// decompressed before searching.
|
/// decompressed before searching.
|
||||||
fn should_decompress(&self, path: &Path) -> bool {
|
fn should_decompress(&self, path: &Path) -> bool {
|
||||||
@ -395,8 +297,10 @@ impl<W: WriteColor> SearchWorker<W> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> io::Result<SearchResult> {
|
) -> io::Result<SearchResult> {
|
||||||
|
use std::{fs::File, process::Stdio};
|
||||||
|
|
||||||
let bin = self.config.preprocessor.as_ref().unwrap();
|
let bin = self.config.preprocessor.as_ref().unwrap();
|
||||||
let mut cmd = Command::new(bin);
|
let mut cmd = std::process::Command::new(bin);
|
||||||
cmd.arg(path).stdin(Stdio::from(File::open(path)?));
|
cmd.arg(path).stdin(Stdio::from(File::open(path)?));
|
||||||
|
|
||||||
let mut rdr = self.command_builder.build(&mut cmd).map_err(|err| {
|
let mut rdr = self.command_builder.build(&mut cmd).map_err(|err| {
|
||||||
@ -473,7 +377,7 @@ impl<W: WriteColor> SearchWorker<W> {
|
|||||||
/// searcher and printer.
|
/// searcher and printer.
|
||||||
fn search_path<M: Matcher, W: WriteColor>(
|
fn search_path<M: Matcher, W: WriteColor>(
|
||||||
matcher: M,
|
matcher: M,
|
||||||
searcher: &mut Searcher,
|
searcher: &mut grep::searcher::Searcher,
|
||||||
printer: &mut Printer<W>,
|
printer: &mut Printer<W>,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> io::Result<SearchResult> {
|
) -> io::Result<SearchResult> {
|
||||||
@ -509,7 +413,7 @@ fn search_path<M: Matcher, W: WriteColor>(
|
|||||||
/// and printer.
|
/// and printer.
|
||||||
fn search_reader<M: Matcher, R: io::Read, W: WriteColor>(
|
fn search_reader<M: Matcher, R: io::Read, W: WriteColor>(
|
||||||
matcher: M,
|
matcher: M,
|
||||||
searcher: &mut Searcher,
|
searcher: &mut grep::searcher::Searcher,
|
||||||
printer: &mut Printer<W>,
|
printer: &mut Printer<W>,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
mut rdr: R,
|
mut rdr: R,
|
||||||
@ -541,8 +445,3 @@ fn search_reader<M: Matcher, R: io::Read, W: WriteColor>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the given duration as fractional seconds.
|
|
||||||
fn fractional_seconds(duration: Duration) -> f64 {
|
|
||||||
(duration.as_secs() as f64) + (duration.subsec_nanos() as f64 * 1e-9)
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.7" #:version
|
version = "0.4.16" #: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
|
||||||
@ -12,26 +12,36 @@ homepage = "https://github.com/BurntSushi/ripgrep/tree/master/crates/globset"
|
|||||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/globset"
|
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/globset"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["regex", "glob", "multiple", "set", "pattern"]
|
keywords = ["regex", "glob", "multiple", "set", "pattern"]
|
||||||
license = "Unlicense/MIT"
|
license = "Unlicense OR MIT"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aho-corasick = "0.7.3"
|
aho-corasick = "1.1.1"
|
||||||
bstr = { version = "0.2.0", default-features = false, features = ["std"] }
|
bstr = { version = "1.6.2", default-features = false, features = ["std"] }
|
||||||
fnv = "1.0.6"
|
log = { version = "0.4.20", optional = true }
|
||||||
log = "0.4.5"
|
serde = { version = "1.0.188", optional = true }
|
||||||
regex = { version = "1.1.5", default-features = false, features = ["perf", "std"] }
|
|
||||||
serde = { version = "1.0.104", optional = true }
|
[dependencies.regex-syntax]
|
||||||
|
version = "0.8.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["std"]
|
||||||
|
|
||||||
|
[dependencies.regex-automata]
|
||||||
|
version = "0.4.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["std", "perf", "syntax", "meta", "nfa", "hybrid"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
glob = "0.3.0"
|
glob = "0.3.1"
|
||||||
lazy_static = "1"
|
serde_json = "1.0.107"
|
||||||
serde_json = "1.0.45"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["log"]
|
||||||
|
# DEPRECATED. It is a no-op. SIMD is done automatically through runtime
|
||||||
|
# dispatch.
|
||||||
simd-accel = []
|
simd-accel = []
|
||||||
serde1 = ["serde"]
|
serde1 = ["serde"]
|
||||||
|
@ -19,7 +19,7 @@ Add this to your `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
globset = "0.3"
|
globset = "0.4"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
@ -78,12 +78,12 @@ assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
|
|||||||
|
|
||||||
This crate implements globs by converting them to regular expressions, and
|
This crate implements globs by converting them to regular expressions, and
|
||||||
executing them with the
|
executing them with the
|
||||||
[`regex`](https://github.com/rust-lang-nursery/regex)
|
[`regex`](https://github.com/rust-lang/regex)
|
||||||
crate.
|
crate.
|
||||||
|
|
||||||
For single glob matching, performance of this crate should be roughly on par
|
For single glob matching, performance of this crate should be roughly on par
|
||||||
with the performance of the
|
with the performance of the
|
||||||
[`glob`](https://github.com/rust-lang-nursery/glob)
|
[`glob`](https://github.com/rust-lang/glob)
|
||||||
crate. (`*_regex` correspond to benchmarks for this library while `*_glob`
|
crate. (`*_regex` correspond to benchmarks for this library while `*_glob`
|
||||||
correspond to benchmarks for the `glob` library.)
|
correspond to benchmarks for the `glob` library.)
|
||||||
Optimizations in the `regex` crate may propel this library past `glob`,
|
Optimizations in the `regex` crate may propel this library past `glob`,
|
||||||
@ -108,7 +108,7 @@ test many_short_glob ... bench: 1,063 ns/iter (+/- 47)
|
|||||||
test many_short_regex_set ... bench: 186 ns/iter (+/- 11)
|
test many_short_regex_set ... bench: 186 ns/iter (+/- 11)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Comparison with the [`glob`](https://github.com/rust-lang-nursery/glob) crate
|
### Comparison with the [`glob`](https://github.com/rust-lang/glob) crate
|
||||||
|
|
||||||
* Supports alternate "or" globs, e.g., `*.{foo,bar}`.
|
* Supports alternate "or" globs, e.g., `*.{foo,bar}`.
|
||||||
* Can match non-UTF-8 file paths correctly.
|
* Can match non-UTF-8 file paths correctly.
|
||||||
|
30
crates/globset/src/fnv.rs
Normal file
30
crates/globset/src/fnv.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/// A convenience alias for creating a hash map with an FNV hasher.
|
||||||
|
pub(crate) type HashMap<K, V> =
|
||||||
|
std::collections::HashMap<K, V, std::hash::BuildHasherDefault<Hasher>>;
|
||||||
|
|
||||||
|
/// A hasher that implements the Fowler–Noll–Vo (FNV) hash.
|
||||||
|
pub(crate) struct Hasher(u64);
|
||||||
|
|
||||||
|
impl Hasher {
|
||||||
|
const OFFSET_BASIS: u64 = 0xcbf29ce484222325;
|
||||||
|
const PRIME: u64 = 0x100000001b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Hasher {
|
||||||
|
fn default() -> Hasher {
|
||||||
|
Hasher(Hasher::OFFSET_BASIS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hasher for Hasher {
|
||||||
|
fn finish(&self) -> u64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
|
for &byte in bytes.iter() {
|
||||||
|
self.0 = self.0 ^ u64::from(byte);
|
||||||
|
self.0 = self.0.wrapping_mul(Hasher::PRIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt::Write;
|
||||||
use std::hash;
|
|
||||||
use std::iter;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::path::{is_separator, Path};
|
use std::path::{is_separator, Path};
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use regex;
|
use regex_automata::meta::Regex;
|
||||||
use regex::bytes::Regex;
|
|
||||||
|
|
||||||
use crate::{new_regex, Candidate, Error, ErrorKind};
|
use crate::{new_regex, Candidate, Error, ErrorKind};
|
||||||
|
|
||||||
@ -18,7 +13,7 @@ use crate::{new_regex, Candidate, Error, ErrorKind};
|
|||||||
/// possible to test whether any of those patterns matches by looking up a
|
/// possible to test whether any of those patterns matches by looking up a
|
||||||
/// file path's extension in a hash table.
|
/// file path's extension in a hash table.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum MatchStrategy {
|
pub(crate) enum MatchStrategy {
|
||||||
/// A pattern matches if and only if the entire file path matches this
|
/// A pattern matches if and only if the entire file path matches this
|
||||||
/// literal string.
|
/// literal string.
|
||||||
Literal(String),
|
Literal(String),
|
||||||
@ -53,7 +48,7 @@ pub enum MatchStrategy {
|
|||||||
|
|
||||||
impl MatchStrategy {
|
impl MatchStrategy {
|
||||||
/// Returns a matching strategy for the given pattern.
|
/// Returns a matching strategy for the given pattern.
|
||||||
pub fn new(pat: &Glob) -> MatchStrategy {
|
pub(crate) fn new(pat: &Glob) -> MatchStrategy {
|
||||||
if let Some(lit) = pat.basename_literal() {
|
if let Some(lit) = pat.basename_literal() {
|
||||||
MatchStrategy::BasenameLiteral(lit)
|
MatchStrategy::BasenameLiteral(lit)
|
||||||
} else if let Some(lit) = pat.literal() {
|
} else if let Some(lit) = pat.literal() {
|
||||||
@ -63,7 +58,7 @@ impl MatchStrategy {
|
|||||||
} else if let Some(prefix) = pat.prefix() {
|
} else if let Some(prefix) = pat.prefix() {
|
||||||
MatchStrategy::Prefix(prefix)
|
MatchStrategy::Prefix(prefix)
|
||||||
} else if let Some((suffix, component)) = pat.suffix() {
|
} else if let Some((suffix, component)) = pat.suffix() {
|
||||||
MatchStrategy::Suffix { suffix: suffix, component: component }
|
MatchStrategy::Suffix { suffix, component }
|
||||||
} else if let Some(ext) = pat.required_ext() {
|
} else if let Some(ext) = pat.required_ext() {
|
||||||
MatchStrategy::RequiredExtension(ext)
|
MatchStrategy::RequiredExtension(ext)
|
||||||
} else {
|
} else {
|
||||||
@ -90,20 +85,20 @@ impl PartialEq for Glob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl hash::Hash for Glob {
|
impl std::hash::Hash for Glob {
|
||||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.glob.hash(state);
|
self.glob.hash(state);
|
||||||
self.opts.hash(state);
|
self.opts.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Glob {
|
impl std::fmt::Display for Glob {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.glob.fmt(f)
|
self.glob.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl str::FromStr for Glob {
|
impl std::str::FromStr for Glob {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(glob: &str) -> Result<Self, Self::Err> {
|
fn from_str(glob: &str) -> Result<Self, Self::Err> {
|
||||||
@ -143,8 +138,6 @@ impl GlobMatcher {
|
|||||||
struct GlobStrategic {
|
struct GlobStrategic {
|
||||||
/// The match strategy to use.
|
/// The match strategy to use.
|
||||||
strategy: MatchStrategy,
|
strategy: MatchStrategy,
|
||||||
/// The underlying pattern.
|
|
||||||
pat: Glob,
|
|
||||||
/// The pattern, as a compiled regex.
|
/// The pattern, as a compiled regex.
|
||||||
re: Regex,
|
re: Regex,
|
||||||
}
|
}
|
||||||
@ -210,6 +203,9 @@ struct GlobOptions {
|
|||||||
/// Whether or not to use `\` to escape special characters.
|
/// Whether or not to use `\` to escape special characters.
|
||||||
/// e.g., when enabled, `\*` will match a literal `*`.
|
/// e.g., when enabled, `\*` will match a literal `*`.
|
||||||
backslash_escape: bool,
|
backslash_escape: bool,
|
||||||
|
/// Whether or not an empty case in an alternate will be removed.
|
||||||
|
/// e.g., when enabled, `{,a}` will match "" and "a".
|
||||||
|
empty_alternates: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobOptions {
|
impl GlobOptions {
|
||||||
@ -218,6 +214,7 @@ impl GlobOptions {
|
|||||||
case_insensitive: false,
|
case_insensitive: false,
|
||||||
literal_separator: false,
|
literal_separator: false,
|
||||||
backslash_escape: !is_separator('\\'),
|
backslash_escape: !is_separator('\\'),
|
||||||
|
empty_alternates: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,14 +222,14 @@ impl GlobOptions {
|
|||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
struct Tokens(Vec<Token>);
|
struct Tokens(Vec<Token>);
|
||||||
|
|
||||||
impl Deref for Tokens {
|
impl std::ops::Deref for Tokens {
|
||||||
type Target = Vec<Token>;
|
type Target = Vec<Token>;
|
||||||
fn deref(&self) -> &Vec<Token> {
|
fn deref(&self) -> &Vec<Token> {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for Tokens {
|
impl std::ops::DerefMut for Tokens {
|
||||||
fn deref_mut(&mut self) -> &mut Vec<Token> {
|
fn deref_mut(&mut self) -> &mut Vec<Token> {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
@ -260,7 +257,7 @@ impl Glob {
|
|||||||
pub fn compile_matcher(&self) -> GlobMatcher {
|
pub fn compile_matcher(&self) -> GlobMatcher {
|
||||||
let re =
|
let re =
|
||||||
new_regex(&self.re).expect("regex compilation shouldn't fail");
|
new_regex(&self.re).expect("regex compilation shouldn't fail");
|
||||||
GlobMatcher { pat: self.clone(), re: re }
|
GlobMatcher { pat: self.clone(), re }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a strategic matcher.
|
/// Returns a strategic matcher.
|
||||||
@ -273,7 +270,7 @@ impl Glob {
|
|||||||
let strategy = MatchStrategy::new(self);
|
let strategy = MatchStrategy::new(self);
|
||||||
let re =
|
let re =
|
||||||
new_regex(&self.re).expect("regex compilation shouldn't fail");
|
new_regex(&self.re).expect("regex compilation shouldn't fail");
|
||||||
GlobStrategic { strategy: strategy, pat: self.clone(), re: re }
|
GlobStrategic { strategy, re }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the original glob pattern used to build this pattern.
|
/// Returns the original glob pattern used to build this pattern.
|
||||||
@ -309,10 +306,8 @@ impl Glob {
|
|||||||
}
|
}
|
||||||
let mut lit = String::new();
|
let mut lit = String::new();
|
||||||
for t in &*self.tokens {
|
for t in &*self.tokens {
|
||||||
match *t {
|
let Token::Literal(c) = *t else { return None };
|
||||||
Token::Literal(c) => lit.push(c),
|
lit.push(c);
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if lit.is_empty() {
|
if lit.is_empty() {
|
||||||
None
|
None
|
||||||
@ -332,13 +327,12 @@ impl Glob {
|
|||||||
if self.opts.case_insensitive {
|
if self.opts.case_insensitive {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let start = match self.tokens.get(0) {
|
let start = match *self.tokens.get(0)? {
|
||||||
Some(&Token::RecursivePrefix) => 1,
|
Token::RecursivePrefix => 1,
|
||||||
Some(_) => 0,
|
_ => 0,
|
||||||
_ => return None,
|
|
||||||
};
|
};
|
||||||
match self.tokens.get(start) {
|
match *self.tokens.get(start)? {
|
||||||
Some(&Token::ZeroOrMore) => {
|
Token::ZeroOrMore => {
|
||||||
// If there was no recursive prefix, then we only permit
|
// If there was no recursive prefix, then we only permit
|
||||||
// `*` if `*` can match a `/`. For example, if `*` can't
|
// `*` if `*` can match a `/`. For example, if `*` can't
|
||||||
// match `/`, then `*.c` doesn't match `foo/bar.c`.
|
// match `/`, then `*.c` doesn't match `foo/bar.c`.
|
||||||
@ -348,8 +342,8 @@ impl Glob {
|
|||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
match self.tokens.get(start + 1) {
|
match *self.tokens.get(start + 1)? {
|
||||||
Some(&Token::Literal('.')) => {}
|
Token::Literal('.') => {}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
let mut lit = ".".to_string();
|
let mut lit = ".".to_string();
|
||||||
@ -403,8 +397,8 @@ impl Glob {
|
|||||||
if self.opts.case_insensitive {
|
if self.opts.case_insensitive {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let (end, need_sep) = match self.tokens.last() {
|
let (end, need_sep) = match *self.tokens.last()? {
|
||||||
Some(&Token::ZeroOrMore) => {
|
Token::ZeroOrMore => {
|
||||||
if self.opts.literal_separator {
|
if self.opts.literal_separator {
|
||||||
// If a trailing `*` can't match a `/`, then we can't
|
// If a trailing `*` can't match a `/`, then we can't
|
||||||
// assume a match of the prefix corresponds to a match
|
// assume a match of the prefix corresponds to a match
|
||||||
@ -416,15 +410,13 @@ impl Glob {
|
|||||||
}
|
}
|
||||||
(self.tokens.len() - 1, false)
|
(self.tokens.len() - 1, false)
|
||||||
}
|
}
|
||||||
Some(&Token::RecursiveSuffix) => (self.tokens.len() - 1, true),
|
Token::RecursiveSuffix => (self.tokens.len() - 1, true),
|
||||||
_ => (self.tokens.len(), false),
|
_ => (self.tokens.len(), false),
|
||||||
};
|
};
|
||||||
let mut lit = String::new();
|
let mut lit = String::new();
|
||||||
for t in &self.tokens[0..end] {
|
for t in &self.tokens[0..end] {
|
||||||
match *t {
|
let Token::Literal(c) = *t else { return None };
|
||||||
Token::Literal(c) => lit.push(c),
|
lit.push(c);
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if need_sep {
|
if need_sep {
|
||||||
lit.push('/');
|
lit.push('/');
|
||||||
@ -453,8 +445,8 @@ impl Glob {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut lit = String::new();
|
let mut lit = String::new();
|
||||||
let (start, entire) = match self.tokens.get(0) {
|
let (start, entire) = match *self.tokens.get(0)? {
|
||||||
Some(&Token::RecursivePrefix) => {
|
Token::RecursivePrefix => {
|
||||||
// We only care if this follows a path component if the next
|
// We only care if this follows a path component if the next
|
||||||
// token is a literal.
|
// token is a literal.
|
||||||
if let Some(&Token::Literal(_)) = self.tokens.get(1) {
|
if let Some(&Token::Literal(_)) = self.tokens.get(1) {
|
||||||
@ -466,8 +458,8 @@ impl Glob {
|
|||||||
}
|
}
|
||||||
_ => (0, false),
|
_ => (0, false),
|
||||||
};
|
};
|
||||||
let start = match self.tokens.get(start) {
|
let start = match *self.tokens.get(start)? {
|
||||||
Some(&Token::ZeroOrMore) => {
|
Token::ZeroOrMore => {
|
||||||
// If literal_separator is enabled, then a `*` can't
|
// If literal_separator is enabled, then a `*` can't
|
||||||
// necessarily match everything, so reporting a suffix match
|
// necessarily match everything, so reporting a suffix match
|
||||||
// as a match of the pattern would be a false positive.
|
// as a match of the pattern would be a false positive.
|
||||||
@ -479,10 +471,8 @@ impl Glob {
|
|||||||
_ => start,
|
_ => start,
|
||||||
};
|
};
|
||||||
for t in &self.tokens[start..] {
|
for t in &self.tokens[start..] {
|
||||||
match *t {
|
let Token::Literal(c) = *t else { return None };
|
||||||
Token::Literal(c) => lit.push(c),
|
lit.push(c);
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if lit.is_empty() || lit == "/" {
|
if lit.is_empty() || lit == "/" {
|
||||||
None
|
None
|
||||||
@ -506,8 +496,8 @@ impl Glob {
|
|||||||
if self.opts.case_insensitive {
|
if self.opts.case_insensitive {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let start = match self.tokens.get(0) {
|
let start = match *self.tokens.get(0)? {
|
||||||
Some(&Token::RecursivePrefix) => 1,
|
Token::RecursivePrefix => 1,
|
||||||
_ => {
|
_ => {
|
||||||
// With nothing to gobble up the parent portion of a path,
|
// With nothing to gobble up the parent portion of a path,
|
||||||
// we can't assume that matching on only the basename is
|
// we can't assume that matching on only the basename is
|
||||||
@ -518,7 +508,7 @@ impl Glob {
|
|||||||
if self.tokens[start..].is_empty() {
|
if self.tokens[start..].is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
for t in &self.tokens[start..] {
|
for t in self.tokens[start..].iter() {
|
||||||
match *t {
|
match *t {
|
||||||
Token::Literal('/') => return None,
|
Token::Literal('/') => return None,
|
||||||
Token::Literal(_) => {} // OK
|
Token::Literal(_) => {} // OK
|
||||||
@ -552,16 +542,11 @@ impl Glob {
|
|||||||
/// The basic format of these patterns is `**/{literal}`, where `{literal}`
|
/// The basic format of these patterns is `**/{literal}`, where `{literal}`
|
||||||
/// does not contain a path separator.
|
/// does not contain a path separator.
|
||||||
fn basename_literal(&self) -> Option<String> {
|
fn basename_literal(&self) -> Option<String> {
|
||||||
let tokens = match self.basename_tokens() {
|
let tokens = self.basename_tokens()?;
|
||||||
None => return None,
|
|
||||||
Some(tokens) => tokens,
|
|
||||||
};
|
|
||||||
let mut lit = String::new();
|
let mut lit = String::new();
|
||||||
for t in tokens {
|
for t in tokens {
|
||||||
match *t {
|
let Token::Literal(c) = *t else { return None };
|
||||||
Token::Literal(c) => lit.push(c),
|
lit.push(c);
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(lit)
|
Some(lit)
|
||||||
}
|
}
|
||||||
@ -572,7 +557,7 @@ impl<'a> GlobBuilder<'a> {
|
|||||||
///
|
///
|
||||||
/// The pattern is not compiled until `build` is called.
|
/// The pattern is not compiled until `build` is called.
|
||||||
pub fn new(glob: &'a str) -> GlobBuilder<'a> {
|
pub fn new(glob: &'a str) -> GlobBuilder<'a> {
|
||||||
GlobBuilder { glob: glob, opts: GlobOptions::default() }
|
GlobBuilder { glob, opts: GlobOptions::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses and builds the pattern.
|
/// Parses and builds the pattern.
|
||||||
@ -602,7 +587,7 @@ impl<'a> GlobBuilder<'a> {
|
|||||||
glob: self.glob.to_string(),
|
glob: self.glob.to_string(),
|
||||||
re: tokens.to_regex_with(&self.opts),
|
re: tokens.to_regex_with(&self.opts),
|
||||||
opts: self.opts,
|
opts: self.opts,
|
||||||
tokens: tokens,
|
tokens,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -635,6 +620,17 @@ impl<'a> GlobBuilder<'a> {
|
|||||||
self.opts.backslash_escape = yes;
|
self.opts.backslash_escape = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Toggle whether an empty pattern in a list of alternates is accepted.
|
||||||
|
///
|
||||||
|
/// For example, if this is set then the glob `foo{,.txt}` will match both
|
||||||
|
/// `foo` and `foo.txt`.
|
||||||
|
///
|
||||||
|
/// By default this is false.
|
||||||
|
pub fn empty_alternates(&mut self, yes: bool) -> &mut GlobBuilder<'a> {
|
||||||
|
self.opts.empty_alternates = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tokens {
|
impl Tokens {
|
||||||
@ -666,7 +662,7 @@ impl Tokens {
|
|||||||
tokens: &[Token],
|
tokens: &[Token],
|
||||||
re: &mut String,
|
re: &mut String,
|
||||||
) {
|
) {
|
||||||
for tok in tokens {
|
for tok in tokens.iter() {
|
||||||
match *tok {
|
match *tok {
|
||||||
Token::Literal(c) => {
|
Token::Literal(c) => {
|
||||||
re.push_str(&char_to_escaped_literal(c));
|
re.push_str(&char_to_escaped_literal(c));
|
||||||
@ -716,7 +712,7 @@ 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);
|
||||||
if !altre.is_empty() {
|
if !altre.is_empty() || options.empty_alternates {
|
||||||
parts.push(altre);
|
parts.push(altre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -724,7 +720,7 @@ impl Tokens {
|
|||||||
// It is possible to have an empty set in which case the
|
// It is possible to have an empty set in which case the
|
||||||
// resulting alternation '()' would be an error.
|
// resulting alternation '()' would be an error.
|
||||||
if !parts.is_empty() {
|
if !parts.is_empty() {
|
||||||
re.push('(');
|
re.push_str("(?:");
|
||||||
re.push_str(&parts.join("|"));
|
re.push_str(&parts.join("|"));
|
||||||
re.push(')');
|
re.push(')');
|
||||||
}
|
}
|
||||||
@ -737,7 +733,9 @@ impl Tokens {
|
|||||||
/// Convert a Unicode scalar value to an escaped string suitable for use as
|
/// Convert a Unicode scalar value to an escaped string suitable for use as
|
||||||
/// a literal in a non-Unicode regex.
|
/// a literal in a non-Unicode regex.
|
||||||
fn char_to_escaped_literal(c: char) -> String {
|
fn char_to_escaped_literal(c: char) -> String {
|
||||||
bytes_to_escaped_literal(&c.to_string().into_bytes())
|
let mut buf = [0; 4];
|
||||||
|
let bytes = c.encode_utf8(&mut buf).as_bytes();
|
||||||
|
bytes_to_escaped_literal(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an arbitrary sequence of bytes to a UTF-8 string. All non-ASCII
|
/// Converts an arbitrary sequence of bytes to a UTF-8 string. All non-ASCII
|
||||||
@ -746,9 +744,12 @@ fn bytes_to_escaped_literal(bs: &[u8]) -> String {
|
|||||||
let mut s = String::with_capacity(bs.len());
|
let mut s = String::with_capacity(bs.len());
|
||||||
for &b in bs {
|
for &b in bs {
|
||||||
if b <= 0x7F {
|
if b <= 0x7F {
|
||||||
s.push_str(®ex::escape(&(b as char).to_string()));
|
regex_syntax::escape_into(
|
||||||
|
char::from(b).encode_utf8(&mut [0; 4]),
|
||||||
|
&mut s,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
s.push_str(&format!("\\x{:02x}", b));
|
write!(&mut s, "\\x{:02x}", b).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s
|
s
|
||||||
@ -757,7 +758,7 @@ fn bytes_to_escaped_literal(bs: &[u8]) -> String {
|
|||||||
struct Parser<'a> {
|
struct Parser<'a> {
|
||||||
glob: &'a str,
|
glob: &'a str,
|
||||||
stack: Vec<Tokens>,
|
stack: Vec<Tokens>,
|
||||||
chars: iter::Peekable<str::Chars<'a>>,
|
chars: std::iter::Peekable<std::str::Chars<'a>>,
|
||||||
prev: Option<char>,
|
prev: Option<char>,
|
||||||
cur: Option<char>,
|
cur: Option<char>,
|
||||||
opts: &'a GlobOptions,
|
opts: &'a GlobOptions,
|
||||||
@ -765,7 +766,7 @@ struct Parser<'a> {
|
|||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
fn error(&self, kind: ErrorKind) -> Error {
|
fn error(&self, kind: ErrorKind) -> Error {
|
||||||
Error { glob: Some(self.glob.to_string()), kind: kind }
|
Error { glob: Some(self.glob.to_string()), kind }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(&mut self) -> Result<(), Error> {
|
fn parse(&mut self) -> Result<(), Error> {
|
||||||
@ -984,7 +985,7 @@ impl<'a> Parser<'a> {
|
|||||||
// it as a literal.
|
// it as a literal.
|
||||||
ranges.push(('-', '-'));
|
ranges.push(('-', '-'));
|
||||||
}
|
}
|
||||||
self.push_token(Token::Class { negated: negated, ranges: ranges })
|
self.push_token(Token::Class { negated, ranges })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bump(&mut self) -> Option<char> {
|
fn bump(&mut self) -> Option<char> {
|
||||||
@ -1022,6 +1023,7 @@ mod tests {
|
|||||||
casei: Option<bool>,
|
casei: Option<bool>,
|
||||||
litsep: Option<bool>,
|
litsep: Option<bool>,
|
||||||
bsesc: Option<bool>,
|
bsesc: Option<bool>,
|
||||||
|
ealtre: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! syntax {
|
macro_rules! syntax {
|
||||||
@ -1061,6 +1063,9 @@ mod tests {
|
|||||||
if let Some(bsesc) = $options.bsesc {
|
if let Some(bsesc) = $options.bsesc {
|
||||||
builder.backslash_escape(bsesc);
|
builder.backslash_escape(bsesc);
|
||||||
}
|
}
|
||||||
|
if let Some(ealtre) = $options.ealtre {
|
||||||
|
builder.empty_alternates(ealtre);
|
||||||
|
}
|
||||||
let pat = builder.build().unwrap();
|
let pat = builder.build().unwrap();
|
||||||
assert_eq!(format!("(?-u){}", $re), pat.regex());
|
assert_eq!(format!("(?-u){}", $re), pat.regex());
|
||||||
}
|
}
|
||||||
@ -1084,6 +1089,9 @@ mod tests {
|
|||||||
if let Some(bsesc) = $options.bsesc {
|
if let Some(bsesc) = $options.bsesc {
|
||||||
builder.backslash_escape(bsesc);
|
builder.backslash_escape(bsesc);
|
||||||
}
|
}
|
||||||
|
if let Some(ealtre) = $options.ealtre {
|
||||||
|
builder.empty_alternates(ealtre);
|
||||||
|
}
|
||||||
let pat = builder.build().unwrap();
|
let pat = builder.build().unwrap();
|
||||||
let matcher = pat.compile_matcher();
|
let matcher = pat.compile_matcher();
|
||||||
let strategic = pat.compile_strategic_matcher();
|
let strategic = pat.compile_strategic_matcher();
|
||||||
@ -1112,6 +1120,9 @@ mod tests {
|
|||||||
if let Some(bsesc) = $options.bsesc {
|
if let Some(bsesc) = $options.bsesc {
|
||||||
builder.backslash_escape(bsesc);
|
builder.backslash_escape(bsesc);
|
||||||
}
|
}
|
||||||
|
if let Some(ealtre) = $options.ealtre {
|
||||||
|
builder.empty_alternates(ealtre);
|
||||||
|
}
|
||||||
let pat = builder.build().unwrap();
|
let pat = builder.build().unwrap();
|
||||||
let matcher = pat.compile_matcher();
|
let matcher = pat.compile_matcher();
|
||||||
let strategic = pat.compile_strategic_matcher();
|
let strategic = pat.compile_strategic_matcher();
|
||||||
@ -1197,13 +1208,23 @@ mod tests {
|
|||||||
syntaxerr!(err_range2, "[z--]", ErrorKind::InvalidRange('z', '-'));
|
syntaxerr!(err_range2, "[z--]", ErrorKind::InvalidRange('z', '-'));
|
||||||
|
|
||||||
const CASEI: Options =
|
const CASEI: Options =
|
||||||
Options { casei: Some(true), litsep: None, bsesc: None };
|
Options { casei: Some(true), litsep: None, bsesc: None, ealtre: None };
|
||||||
const SLASHLIT: Options =
|
const SLASHLIT: Options =
|
||||||
Options { casei: None, litsep: Some(true), bsesc: None };
|
Options { casei: None, litsep: Some(true), bsesc: None, ealtre: None };
|
||||||
const NOBSESC: Options =
|
const NOBSESC: Options = Options {
|
||||||
Options { casei: None, litsep: None, bsesc: Some(false) };
|
casei: None,
|
||||||
|
litsep: None,
|
||||||
|
bsesc: Some(false),
|
||||||
|
ealtre: None,
|
||||||
|
};
|
||||||
const BSESC: Options =
|
const BSESC: Options =
|
||||||
Options { casei: None, litsep: None, bsesc: Some(true) };
|
Options { casei: None, litsep: None, bsesc: Some(true), ealtre: None };
|
||||||
|
const EALTRE: Options = Options {
|
||||||
|
casei: None,
|
||||||
|
litsep: None,
|
||||||
|
bsesc: Some(true),
|
||||||
|
ealtre: Some(true),
|
||||||
|
};
|
||||||
|
|
||||||
toregex!(re_casei, "a", "(?i)^a$", &CASEI);
|
toregex!(re_casei, "a", "(?i)^a$", &CASEI);
|
||||||
|
|
||||||
@ -1244,6 +1265,7 @@ mod tests {
|
|||||||
toregex!(re32, "/a**", r"^/a.*.*$");
|
toregex!(re32, "/a**", r"^/a.*.*$");
|
||||||
toregex!(re33, "/**a", r"^/.*.*a$");
|
toregex!(re33, "/**a", r"^/.*.*a$");
|
||||||
toregex!(re34, "/a**b", r"^/a.*.*b$");
|
toregex!(re34, "/a**b", r"^/a.*.*b$");
|
||||||
|
toregex!(re35, "{a,b}", r"^(?:b|a)$");
|
||||||
|
|
||||||
matches!(match1, "a", "a");
|
matches!(match1, "a", "a");
|
||||||
matches!(match2, "a*b", "a_b");
|
matches!(match2, "a*b", "a_b");
|
||||||
@ -1328,6 +1350,9 @@ mod tests {
|
|||||||
matches!(matchalt11, "{*.foo,*.bar,*.wat}", "test.foo");
|
matches!(matchalt11, "{*.foo,*.bar,*.wat}", "test.foo");
|
||||||
matches!(matchalt12, "{*.foo,*.bar,*.wat}", "test.bar");
|
matches!(matchalt12, "{*.foo,*.bar,*.wat}", "test.bar");
|
||||||
matches!(matchalt13, "{*.foo,*.bar,*.wat}", "test.wat");
|
matches!(matchalt13, "{*.foo,*.bar,*.wat}", "test.wat");
|
||||||
|
matches!(matchalt14, "foo{,.txt}", "foo.txt");
|
||||||
|
nmatches!(matchalt15, "foo{,.txt}", "foo");
|
||||||
|
matches!(matchalt16, "foo{,.txt}", "foo", EALTRE);
|
||||||
|
|
||||||
matches!(matchslash1, "abc/def", "abc/def", SLASHLIT);
|
matches!(matchslash1, "abc/def", "abc/def", SLASHLIT);
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@ -1427,6 +1452,9 @@ mod tests {
|
|||||||
if let Some(bsesc) = $options.bsesc {
|
if let Some(bsesc) = $options.bsesc {
|
||||||
builder.backslash_escape(bsesc);
|
builder.backslash_escape(bsesc);
|
||||||
}
|
}
|
||||||
|
if let Some(ealtre) = $options.ealtre {
|
||||||
|
builder.empty_alternates(ealtre);
|
||||||
|
}
|
||||||
let pat = builder.build().unwrap();
|
let pat = builder.build().unwrap();
|
||||||
assert_eq!($expect, pat.$which());
|
assert_eq!($expect, pat.$which());
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,9 @@ Glob set matching is the process of matching one or more glob patterns against
|
|||||||
a single candidate path simultaneously, and returning all of the globs that
|
a single candidate path simultaneously, and returning all of the globs that
|
||||||
matched. For example, given this set of globs:
|
matched. For example, given this set of globs:
|
||||||
|
|
||||||
```ignore
|
* `*.rs`
|
||||||
*.rs
|
* `src/lib.rs`
|
||||||
src/lib.rs
|
* `src/**/foo.rs`
|
||||||
src/**/foo.rs
|
|
||||||
```
|
|
||||||
|
|
||||||
and a path `src/bar/baz/foo.rs`, then the set would report the first and third
|
and a path `src/bar/baz/foo.rs`, then the set would report the first and third
|
||||||
globs as matching.
|
globs as matching.
|
||||||
@ -19,7 +17,6 @@ globs as matching.
|
|||||||
This example shows how to match a single glob against a single file path.
|
This example shows how to match a single glob against a single file path.
|
||||||
|
|
||||||
```
|
```
|
||||||
# fn example() -> Result<(), globset::Error> {
|
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
|
|
||||||
let glob = Glob::new("*.rs")?.compile_matcher();
|
let glob = Glob::new("*.rs")?.compile_matcher();
|
||||||
@ -27,7 +24,7 @@ let glob = Glob::new("*.rs")?.compile_matcher();
|
|||||||
assert!(glob.is_match("foo.rs"));
|
assert!(glob.is_match("foo.rs"));
|
||||||
assert!(glob.is_match("foo/bar.rs"));
|
assert!(glob.is_match("foo/bar.rs"));
|
||||||
assert!(!glob.is_match("Cargo.toml"));
|
assert!(!glob.is_match("Cargo.toml"));
|
||||||
# Ok(()) } example().unwrap();
|
# Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
```
|
```
|
||||||
|
|
||||||
# Example: configuring a glob matcher
|
# Example: configuring a glob matcher
|
||||||
@ -36,7 +33,6 @@ This example shows how to use a `GlobBuilder` to configure aspects of match
|
|||||||
semantics. In this example, we prevent wildcards from matching path separators.
|
semantics. In this example, we prevent wildcards from matching path separators.
|
||||||
|
|
||||||
```
|
```
|
||||||
# fn example() -> Result<(), globset::Error> {
|
|
||||||
use globset::GlobBuilder;
|
use globset::GlobBuilder;
|
||||||
|
|
||||||
let glob = GlobBuilder::new("*.rs")
|
let glob = GlobBuilder::new("*.rs")
|
||||||
@ -45,7 +41,7 @@ let glob = GlobBuilder::new("*.rs")
|
|||||||
assert!(glob.is_match("foo.rs"));
|
assert!(glob.is_match("foo.rs"));
|
||||||
assert!(!glob.is_match("foo/bar.rs")); // no longer matches
|
assert!(!glob.is_match("foo/bar.rs")); // no longer matches
|
||||||
assert!(!glob.is_match("Cargo.toml"));
|
assert!(!glob.is_match("Cargo.toml"));
|
||||||
# Ok(()) } example().unwrap();
|
# Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
```
|
```
|
||||||
|
|
||||||
# Example: match multiple globs at once
|
# Example: match multiple globs at once
|
||||||
@ -53,7 +49,6 @@ assert!(!glob.is_match("Cargo.toml"));
|
|||||||
This example shows how to match multiple glob patterns at once.
|
This example shows how to match multiple glob patterns at once.
|
||||||
|
|
||||||
```
|
```
|
||||||
# fn example() -> Result<(), globset::Error> {
|
|
||||||
use globset::{Glob, GlobSetBuilder};
|
use globset::{Glob, GlobSetBuilder};
|
||||||
|
|
||||||
let mut builder = GlobSetBuilder::new();
|
let mut builder = GlobSetBuilder::new();
|
||||||
@ -65,7 +60,7 @@ builder.add(Glob::new("src/**/foo.rs")?);
|
|||||||
let set = builder.build()?;
|
let set = builder.build()?;
|
||||||
|
|
||||||
assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
|
assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
|
||||||
# Ok(()) } example().unwrap();
|
# Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
```
|
```
|
||||||
|
|
||||||
# Syntax
|
# Syntax
|
||||||
@ -103,28 +98,47 @@ or to enable case insensitive matching.
|
|||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::{
|
||||||
use std::collections::{BTreeMap, HashMap};
|
borrow::Cow,
|
||||||
use std::error::Error as StdError;
|
panic::{RefUnwindSafe, UnwindSafe},
|
||||||
use std::fmt;
|
path::Path,
|
||||||
use std::hash;
|
sync::Arc,
|
||||||
use std::path::Path;
|
};
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use aho_corasick::AhoCorasick;
|
use {
|
||||||
use bstr::{ByteSlice, ByteVec, B};
|
aho_corasick::AhoCorasick,
|
||||||
use regex::bytes::{Regex, RegexBuilder, RegexSet};
|
bstr::{ByteSlice, ByteVec, B},
|
||||||
|
regex_automata::{
|
||||||
|
meta::Regex,
|
||||||
|
util::pool::{Pool, PoolGuard},
|
||||||
|
PatternSet,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
glob::MatchStrategy,
|
||||||
|
pathutil::{file_name, file_name_ext, normalize_path},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::glob::MatchStrategy;
|
|
||||||
pub use crate::glob::{Glob, GlobBuilder, GlobMatcher};
|
pub use crate::glob::{Glob, GlobBuilder, GlobMatcher};
|
||||||
use crate::pathutil::{file_name, file_name_ext, normalize_path};
|
|
||||||
|
|
||||||
|
mod fnv;
|
||||||
mod glob;
|
mod glob;
|
||||||
mod pathutil;
|
mod pathutil;
|
||||||
|
|
||||||
#[cfg(feature = "serde1")]
|
#[cfg(feature = "serde1")]
|
||||||
mod serde_impl;
|
mod serde_impl;
|
||||||
|
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
macro_rules! debug {
|
||||||
|
($($token:tt)*) => (::log::debug!($($token)*);)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "log"))]
|
||||||
|
macro_rules! debug {
|
||||||
|
($($token:tt)*) => {};
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents an error that can occur when parsing a glob pattern.
|
/// Represents an error that can occur when parsing a glob pattern.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
@ -171,7 +185,7 @@ pub enum ErrorKind {
|
|||||||
__Nonexhaustive,
|
__Nonexhaustive,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for Error {
|
impl std::error::Error for Error {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
self.kind.description()
|
self.kind.description()
|
||||||
}
|
}
|
||||||
@ -217,8 +231,8 @@ impl ErrorKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.glob {
|
match self.glob {
|
||||||
None => self.kind.fmt(f),
|
None => self.kind.fmt(f),
|
||||||
Some(ref glob) => {
|
Some(ref glob) => {
|
||||||
@ -228,8 +242,8 @@ impl fmt::Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ErrorKind {
|
impl std::fmt::Display for ErrorKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
ErrorKind::InvalidRecursive
|
ErrorKind::InvalidRecursive
|
||||||
| ErrorKind::UnclosedClass
|
| ErrorKind::UnclosedClass
|
||||||
@ -247,30 +261,40 @@ impl fmt::Display for ErrorKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_regex(pat: &str) -> Result<Regex, Error> {
|
fn new_regex(pat: &str) -> Result<Regex, Error> {
|
||||||
RegexBuilder::new(pat)
|
let syntax = regex_automata::util::syntax::Config::new()
|
||||||
.dot_matches_new_line(true)
|
.utf8(false)
|
||||||
.size_limit(10 * (1 << 20))
|
.dot_matches_new_line(true);
|
||||||
.dfa_size_limit(10 * (1 << 20))
|
let config = Regex::config()
|
||||||
.build()
|
.utf8_empty(false)
|
||||||
.map_err(|err| Error {
|
.nfa_size_limit(Some(10 * (1 << 20)))
|
||||||
|
.hybrid_cache_capacity(10 * (1 << 20));
|
||||||
|
Regex::builder().syntax(syntax).configure(config).build(pat).map_err(
|
||||||
|
|err| Error {
|
||||||
glob: Some(pat.to_string()),
|
glob: Some(pat.to_string()),
|
||||||
kind: ErrorKind::Regex(err.to_string()),
|
kind: ErrorKind::Regex(err.to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_regex_set(pats: Vec<String>) -> Result<Regex, Error> {
|
||||||
|
let syntax = regex_automata::util::syntax::Config::new()
|
||||||
|
.utf8(false)
|
||||||
|
.dot_matches_new_line(true);
|
||||||
|
let config = Regex::config()
|
||||||
|
.match_kind(regex_automata::MatchKind::All)
|
||||||
|
.utf8_empty(false)
|
||||||
|
.nfa_size_limit(Some(10 * (1 << 20)))
|
||||||
|
.hybrid_cache_capacity(10 * (1 << 20));
|
||||||
|
Regex::builder()
|
||||||
|
.syntax(syntax)
|
||||||
|
.configure(config)
|
||||||
|
.build_many(&pats)
|
||||||
|
.map_err(|err| Error {
|
||||||
|
glob: None,
|
||||||
|
kind: ErrorKind::Regex(err.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_regex_set<I, S>(pats: I) -> Result<RegexSet, Error>
|
|
||||||
where
|
|
||||||
S: AsRef<str>,
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
{
|
|
||||||
RegexSet::new(pats).map_err(|err| Error {
|
|
||||||
glob: None,
|
|
||||||
kind: ErrorKind::Regex(err.to_string()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type Fnv = hash::BuildHasherDefault<fnv::FnvHasher>;
|
|
||||||
|
|
||||||
/// GlobSet represents a group of globs that can be matched together in a
|
/// GlobSet represents a group of globs that can be matched together in a
|
||||||
/// single pass.
|
/// single pass.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -280,6 +304,14 @@ pub struct GlobSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GlobSet {
|
impl GlobSet {
|
||||||
|
/// Create a new [`GlobSetBuilder`]. A `GlobSetBuilder` can be used to add
|
||||||
|
/// new patterns. Once all patterns have been added, `build` should be
|
||||||
|
/// called to produce a `GlobSet`, which can then be used for matching.
|
||||||
|
#[inline]
|
||||||
|
pub fn builder() -> GlobSetBuilder {
|
||||||
|
GlobSetBuilder::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create an empty `GlobSet`. An empty set matches nothing.
|
/// Create an empty `GlobSet`. An empty set matches nothing.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn empty() -> GlobSet {
|
pub fn empty() -> GlobSet {
|
||||||
@ -413,12 +445,12 @@ impl GlobSet {
|
|||||||
required_exts.add(i, ext, p.regex().to_owned());
|
required_exts.add(i, ext, p.regex().to_owned());
|
||||||
}
|
}
|
||||||
MatchStrategy::Regex => {
|
MatchStrategy::Regex => {
|
||||||
log::debug!("glob converted to regex: {:?}", p);
|
debug!("glob converted to regex: {:?}", p);
|
||||||
regexes.add(i, p.regex().to_owned());
|
regexes.add(i, p.regex().to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::debug!(
|
debug!(
|
||||||
"built glob set; {} literals, {} basenames, {} extensions, \
|
"built glob set; {} literals, {} basenames, {} extensions, \
|
||||||
{} prefixes, {} suffixes, {} required extensions, {} regexes",
|
{} prefixes, {} suffixes, {} required extensions, {} regexes",
|
||||||
lits.0.len(),
|
lits.0.len(),
|
||||||
@ -461,9 +493,9 @@ pub struct GlobSetBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GlobSetBuilder {
|
impl GlobSetBuilder {
|
||||||
/// Create a new GlobSetBuilder. A GlobSetBuilder can be used to add new
|
/// Create a new `GlobSetBuilder`. A `GlobSetBuilder` can be used to add new
|
||||||
/// patterns. Once all patterns have been added, `build` should be called
|
/// patterns. Once all patterns have been added, `build` should be called
|
||||||
/// to produce a `GlobSet`, which can then be used for matching.
|
/// to produce a [`GlobSet`], which can then be used for matching.
|
||||||
pub fn new() -> GlobSetBuilder {
|
pub fn new() -> GlobSetBuilder {
|
||||||
GlobSetBuilder { pats: vec![] }
|
GlobSetBuilder { pats: vec![] }
|
||||||
}
|
}
|
||||||
@ -488,20 +520,30 @@ impl GlobSetBuilder {
|
|||||||
/// Constructing candidates has a very small cost associated with it, so
|
/// Constructing candidates has a very small cost associated with it, so
|
||||||
/// callers may find it beneficial to amortize that cost when matching a single
|
/// callers may find it beneficial to amortize that cost when matching a single
|
||||||
/// path against multiple globs or sets of globs.
|
/// path against multiple globs or sets of globs.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct Candidate<'a> {
|
pub struct Candidate<'a> {
|
||||||
path: Cow<'a, [u8]>,
|
path: Cow<'a, [u8]>,
|
||||||
basename: Cow<'a, [u8]>,
|
basename: Cow<'a, [u8]>,
|
||||||
ext: Cow<'a, [u8]>,
|
ext: Cow<'a, [u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Debug for Candidate<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Candidate")
|
||||||
|
.field("path", &self.path.as_bstr())
|
||||||
|
.field("basename", &self.basename.as_bstr())
|
||||||
|
.field("ext", &self.ext.as_bstr())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Candidate<'a> {
|
impl<'a> Candidate<'a> {
|
||||||
/// Create a new candidate for matching from the given path.
|
/// Create a new candidate for matching from the given path.
|
||||||
pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
|
pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
|
||||||
let path = normalize_path(Vec::from_path_lossy(path.as_ref()));
|
let path = normalize_path(Vec::from_path_lossy(path.as_ref()));
|
||||||
let basename = file_name(&path).unwrap_or(Cow::Borrowed(B("")));
|
let basename = file_name(&path).unwrap_or(Cow::Borrowed(B("")));
|
||||||
let ext = file_name_ext(&basename).unwrap_or(Cow::Borrowed(B("")));
|
let ext = file_name_ext(&basename).unwrap_or(Cow::Borrowed(B("")));
|
||||||
Candidate { path: path, basename: basename, ext: ext }
|
Candidate { path, basename, ext }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_prefix(&self, max: usize) -> &[u8] {
|
fn path_prefix(&self, max: usize) -> &[u8] {
|
||||||
@ -565,11 +607,11 @@ impl GlobSetMatchStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct LiteralStrategy(BTreeMap<Vec<u8>, Vec<usize>>);
|
struct LiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
|
||||||
|
|
||||||
impl LiteralStrategy {
|
impl LiteralStrategy {
|
||||||
fn new() -> LiteralStrategy {
|
fn new() -> LiteralStrategy {
|
||||||
LiteralStrategy(BTreeMap::new())
|
LiteralStrategy(fnv::HashMap::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(&mut self, global_index: usize, lit: String) {
|
fn add(&mut self, global_index: usize, lit: String) {
|
||||||
@ -593,11 +635,11 @@ impl LiteralStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct BasenameLiteralStrategy(BTreeMap<Vec<u8>, Vec<usize>>);
|
struct BasenameLiteralStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
|
||||||
|
|
||||||
impl BasenameLiteralStrategy {
|
impl BasenameLiteralStrategy {
|
||||||
fn new() -> BasenameLiteralStrategy {
|
fn new() -> BasenameLiteralStrategy {
|
||||||
BasenameLiteralStrategy(BTreeMap::new())
|
BasenameLiteralStrategy(fnv::HashMap::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(&mut self, global_index: usize, lit: String) {
|
fn add(&mut self, global_index: usize, lit: String) {
|
||||||
@ -627,11 +669,11 @@ impl BasenameLiteralStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct ExtensionStrategy(HashMap<Vec<u8>, Vec<usize>, Fnv>);
|
struct ExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<usize>>);
|
||||||
|
|
||||||
impl ExtensionStrategy {
|
impl ExtensionStrategy {
|
||||||
fn new() -> ExtensionStrategy {
|
fn new() -> ExtensionStrategy {
|
||||||
ExtensionStrategy(HashMap::with_hasher(Fnv::default()))
|
ExtensionStrategy(fnv::HashMap::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(&mut self, global_index: usize, ext: String) {
|
fn add(&mut self, global_index: usize, ext: String) {
|
||||||
@ -725,7 +767,7 @@ impl SuffixStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct RequiredExtensionStrategy(HashMap<Vec<u8>, Vec<(usize, Regex)>, Fnv>);
|
struct RequiredExtensionStrategy(fnv::HashMap<Vec<u8>, Vec<(usize, Regex)>>);
|
||||||
|
|
||||||
impl RequiredExtensionStrategy {
|
impl RequiredExtensionStrategy {
|
||||||
fn is_match(&self, candidate: &Candidate<'_>) -> bool {
|
fn is_match(&self, candidate: &Candidate<'_>) -> bool {
|
||||||
@ -766,10 +808,22 @@ impl RequiredExtensionStrategy {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct RegexSetStrategy {
|
struct RegexSetStrategy {
|
||||||
matcher: RegexSet,
|
matcher: Regex,
|
||||||
map: Vec<usize>,
|
map: Vec<usize>,
|
||||||
|
// We use a pool of PatternSets to hopefully allocating a fresh one on each
|
||||||
|
// call.
|
||||||
|
//
|
||||||
|
// TODO: In the next semver breaking release, we should drop this pool and
|
||||||
|
// expose an opaque type that wraps PatternSet. Then callers can provide
|
||||||
|
// it to `matches_into` directly. Callers might still want to use a pool
|
||||||
|
// or similar to amortize allocation, but that matches the status quo and
|
||||||
|
// absolves us of needing to do it here.
|
||||||
|
patset: Arc<Pool<PatternSet, PatternSetPoolFn>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PatternSetPoolFn =
|
||||||
|
Box<dyn Fn() -> PatternSet + Send + Sync + UnwindSafe + RefUnwindSafe>;
|
||||||
|
|
||||||
impl RegexSetStrategy {
|
impl RegexSetStrategy {
|
||||||
fn is_match(&self, candidate: &Candidate<'_>) -> bool {
|
fn is_match(&self, candidate: &Candidate<'_>) -> bool {
|
||||||
self.matcher.is_match(candidate.path.as_bytes())
|
self.matcher.is_match(candidate.path.as_bytes())
|
||||||
@ -780,9 +834,14 @@ impl RegexSetStrategy {
|
|||||||
candidate: &Candidate<'_>,
|
candidate: &Candidate<'_>,
|
||||||
matches: &mut Vec<usize>,
|
matches: &mut Vec<usize>,
|
||||||
) {
|
) {
|
||||||
for i in self.matcher.matches(candidate.path.as_bytes()) {
|
let input = regex_automata::Input::new(candidate.path.as_bytes());
|
||||||
|
let mut patset = self.patset.get();
|
||||||
|
patset.clear();
|
||||||
|
self.matcher.which_overlapping_matches(&input, &mut patset);
|
||||||
|
for i in patset.iter() {
|
||||||
matches.push(self.map[i]);
|
matches.push(self.map[i]);
|
||||||
}
|
}
|
||||||
|
PoolGuard::put(patset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -808,7 +867,7 @@ impl MultiStrategyBuilder {
|
|||||||
|
|
||||||
fn prefix(self) -> PrefixStrategy {
|
fn prefix(self) -> PrefixStrategy {
|
||||||
PrefixStrategy {
|
PrefixStrategy {
|
||||||
matcher: AhoCorasick::new_auto_configured(&self.literals),
|
matcher: AhoCorasick::new(&self.literals).unwrap(),
|
||||||
map: self.map,
|
map: self.map,
|
||||||
longest: self.longest,
|
longest: self.longest,
|
||||||
}
|
}
|
||||||
@ -816,28 +875,33 @@ impl MultiStrategyBuilder {
|
|||||||
|
|
||||||
fn suffix(self) -> SuffixStrategy {
|
fn suffix(self) -> SuffixStrategy {
|
||||||
SuffixStrategy {
|
SuffixStrategy {
|
||||||
matcher: AhoCorasick::new_auto_configured(&self.literals),
|
matcher: AhoCorasick::new(&self.literals).unwrap(),
|
||||||
map: self.map,
|
map: self.map,
|
||||||
longest: self.longest,
|
longest: self.longest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn regex_set(self) -> Result<RegexSetStrategy, Error> {
|
fn regex_set(self) -> Result<RegexSetStrategy, Error> {
|
||||||
|
let matcher = new_regex_set(self.literals)?;
|
||||||
|
let pattern_len = matcher.pattern_len();
|
||||||
|
let create: PatternSetPoolFn =
|
||||||
|
Box::new(move || PatternSet::new(pattern_len));
|
||||||
Ok(RegexSetStrategy {
|
Ok(RegexSetStrategy {
|
||||||
matcher: new_regex_set(self.literals)?,
|
matcher,
|
||||||
map: self.map,
|
map: self.map,
|
||||||
|
patset: Arc::new(Pool::new(create)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct RequiredExtensionStrategyBuilder(
|
struct RequiredExtensionStrategyBuilder(
|
||||||
HashMap<Vec<u8>, Vec<(usize, String)>>,
|
fnv::HashMap<Vec<u8>, Vec<(usize, String)>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl RequiredExtensionStrategyBuilder {
|
impl RequiredExtensionStrategyBuilder {
|
||||||
fn new() -> RequiredExtensionStrategyBuilder {
|
fn new() -> RequiredExtensionStrategyBuilder {
|
||||||
RequiredExtensionStrategyBuilder(HashMap::new())
|
RequiredExtensionStrategyBuilder(fnv::HashMap::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(&mut self, global_index: usize, ext: String, regex: String) {
|
fn add(&mut self, global_index: usize, ext: String, regex: String) {
|
||||||
@ -848,7 +912,7 @@ impl RequiredExtensionStrategyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(self) -> Result<RequiredExtensionStrategy, Error> {
|
fn build(self) -> Result<RequiredExtensionStrategy, Error> {
|
||||||
let mut exts = HashMap::with_hasher(Fnv::default());
|
let mut exts = fnv::HashMap::default();
|
||||||
for (ext, regexes) in self.0.into_iter() {
|
for (ext, regexes) in self.0.into_iter() {
|
||||||
exts.insert(ext.clone(), vec![]);
|
exts.insert(ext.clone(), vec![]);
|
||||||
for (global_index, regex) in regexes {
|
for (global_index, regex) in regexes {
|
||||||
@ -860,11 +924,48 @@ impl RequiredExtensionStrategyBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Escape meta-characters within the given glob pattern.
|
||||||
|
///
|
||||||
|
/// The escaping works by surrounding meta-characters with brackets. For
|
||||||
|
/// example, `*` becomes `[*]`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use globset::escape;
|
||||||
|
///
|
||||||
|
/// assert_eq!(escape("foo*bar"), "foo[*]bar");
|
||||||
|
/// assert_eq!(escape("foo?bar"), "foo[?]bar");
|
||||||
|
/// assert_eq!(escape("foo[bar"), "foo[[]bar");
|
||||||
|
/// assert_eq!(escape("foo]bar"), "foo[]]bar");
|
||||||
|
/// assert_eq!(escape("foo{bar"), "foo[{]bar");
|
||||||
|
/// assert_eq!(escape("foo}bar"), "foo[}]bar");
|
||||||
|
/// ```
|
||||||
|
pub fn escape(s: &str) -> String {
|
||||||
|
let mut escaped = String::with_capacity(s.len());
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
// note that ! does not need escaping because it is only special
|
||||||
|
// inside brackets
|
||||||
|
'?' | '*' | '[' | ']' | '{' | '}' => {
|
||||||
|
escaped.push('[');
|
||||||
|
escaped.push(c);
|
||||||
|
escaped.push(']');
|
||||||
|
}
|
||||||
|
c => {
|
||||||
|
escaped.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{GlobSet, GlobSetBuilder};
|
|
||||||
use crate::glob::Glob;
|
use crate::glob::Glob;
|
||||||
|
|
||||||
|
use super::{GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_works() {
|
fn set_works() {
|
||||||
let mut builder = GlobSetBuilder::new();
|
let mut builder = GlobSetBuilder::new();
|
||||||
@ -899,4 +1000,36 @@ mod tests {
|
|||||||
assert!(!set.is_match(""));
|
assert!(!set.is_match(""));
|
||||||
assert!(!set.is_match("a"));
|
assert!(!set.is_match("a"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape() {
|
||||||
|
use super::escape;
|
||||||
|
assert_eq!("foo", escape("foo"));
|
||||||
|
assert_eq!("foo[*]", escape("foo*"));
|
||||||
|
assert_eq!("[[][]]", escape("[]"));
|
||||||
|
assert_eq!("[*][?]", escape("*?"));
|
||||||
|
assert_eq!("src/[*][*]/[*].rs", escape("src/**/*.rs"));
|
||||||
|
assert_eq!("bar[[]ab[]]baz", escape("bar[ab]baz"));
|
||||||
|
assert_eq!("bar[[]!![]]!baz", escape("bar[!!]!baz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tests that regex matching doesn't "remember" the results of
|
||||||
|
// previous searches. That is, if any memory is reused from a previous
|
||||||
|
// search, then it should be cleared first.
|
||||||
|
#[test]
|
||||||
|
fn set_does_not_remember() {
|
||||||
|
let mut builder = GlobSetBuilder::new();
|
||||||
|
builder.add(Glob::new("*foo*").unwrap());
|
||||||
|
builder.add(Glob::new("*bar*").unwrap());
|
||||||
|
builder.add(Glob::new("*quux*").unwrap());
|
||||||
|
let set = builder.build().unwrap();
|
||||||
|
|
||||||
|
let matches = set.matches("ZfooZquuxZ");
|
||||||
|
assert_eq!(2, matches.len());
|
||||||
|
assert_eq!(0, matches[0]);
|
||||||
|
assert_eq!(2, matches[1]);
|
||||||
|
|
||||||
|
let matches = set.matches("nada");
|
||||||
|
assert_eq!(0, matches.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,10 @@ use bstr::{ByteSlice, ByteVec};
|
|||||||
|
|
||||||
/// The final component of the path, if it is a normal file.
|
/// The final component of the path, if it is a normal file.
|
||||||
///
|
///
|
||||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
/// If the path terminates in `.`, `..`, or consists solely of a root of
|
||||||
/// file_name will return None.
|
/// prefix, file_name will return None.
|
||||||
pub fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
pub(crate) fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
||||||
if path.is_empty() {
|
if path.last_byte().map_or(true, |b| b == b'.') {
|
||||||
return None;
|
|
||||||
} else if path.last_byte() == Some(b'.') {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
|
let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
|
||||||
@ -27,7 +25,7 @@ pub fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
|||||||
///
|
///
|
||||||
/// Note that this does NOT match the semantics of std::path::Path::extension.
|
/// Note that this does NOT match the semantics of std::path::Path::extension.
|
||||||
/// Namely, the extension includes the `.` and matching is otherwise more
|
/// Namely, the extension includes the `.` and matching is otherwise more
|
||||||
/// liberal. Specifically, the extenion is:
|
/// liberal. Specifically, the extension is:
|
||||||
///
|
///
|
||||||
/// * None, if the file name given is empty;
|
/// * None, if the file name given is empty;
|
||||||
/// * None, if there is no embedded `.`;
|
/// * None, if there is no embedded `.`;
|
||||||
@ -39,7 +37,9 @@ pub fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
|||||||
/// a pattern like `*.rs` is obviously trying to match files with a `rs`
|
/// a pattern like `*.rs` is obviously trying to match files with a `rs`
|
||||||
/// extension, but it also matches files like `.rs`, which doesn't have an
|
/// extension, but it also matches files like `.rs`, which doesn't have an
|
||||||
/// extension according to std::path::Path::extension.
|
/// extension according to std::path::Path::extension.
|
||||||
pub fn file_name_ext<'a>(name: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
pub(crate) fn file_name_ext<'a>(
|
||||||
|
name: &Cow<'a, [u8]>,
|
||||||
|
) -> Option<Cow<'a, [u8]>> {
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ pub fn file_name_ext<'a>(name: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
|||||||
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
|
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
|
||||||
/// that recognize other characters as separators.
|
/// that recognize other characters as separators.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn normalize_path(path: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
|
pub(crate) fn normalize_path(path: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
|
||||||
// UNIX only uses /, so we're good.
|
// UNIX only uses /, so we're good.
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
@ -68,11 +68,11 @@ pub fn normalize_path(path: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
|
|||||||
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
|
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
|
||||||
/// that recognize other characters as separators.
|
/// that recognize other characters as separators.
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> {
|
pub(crate) fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> {
|
||||||
use std::path::is_separator;
|
use std::path::is_separator;
|
||||||
|
|
||||||
for i in 0..path.len() {
|
for i in 0..path.len() {
|
||||||
if path[i] == b'/' || !is_separator(path[i] as char) {
|
if path[i] == b'/' || !is_separator(char::from(path[i])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
path.to_mut()[i] = b'/';
|
path.to_mut()[i] = b'/';
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use serde::de::Error;
|
use serde::{
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
de::{Error, SeqAccess, Visitor},
|
||||||
|
{Deserialize, Deserializer, Serialize, Serializer},
|
||||||
|
};
|
||||||
|
|
||||||
use Glob;
|
use crate::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
impl Serialize for Glob {
|
impl Serialize for Glob {
|
||||||
fn serialize<S: Serializer>(
|
fn serialize<S: Serializer>(
|
||||||
@ -12,18 +14,98 @@ impl Serialize for Glob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GlobVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for GlobVisitor {
|
||||||
|
type Value = Glob;
|
||||||
|
|
||||||
|
fn expecting(
|
||||||
|
&self,
|
||||||
|
formatter: &mut std::fmt::Formatter,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a glob pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: Error,
|
||||||
|
{
|
||||||
|
Glob::new(v).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Glob {
|
impl<'de> Deserialize<'de> for Glob {
|
||||||
fn deserialize<D: Deserializer<'de>>(
|
fn deserialize<D: Deserializer<'de>>(
|
||||||
deserializer: D,
|
deserializer: D,
|
||||||
) -> Result<Self, D::Error> {
|
) -> Result<Self, D::Error> {
|
||||||
let glob = <&str as Deserialize>::deserialize(deserializer)?;
|
deserializer.deserialize_str(GlobVisitor)
|
||||||
Glob::new(glob).map_err(D::Error::custom)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlobSetVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for GlobSetVisitor {
|
||||||
|
type Value = GlobSet;
|
||||||
|
|
||||||
|
fn expecting(
|
||||||
|
&self,
|
||||||
|
formatter: &mut std::fmt::Formatter,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
formatter.write_str("an array of glob patterns")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut builder = GlobSetBuilder::new();
|
||||||
|
while let Some(glob) = seq.next_element()? {
|
||||||
|
builder.add(glob);
|
||||||
|
}
|
||||||
|
builder.build().map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for GlobSet {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Self, D::Error> {
|
||||||
|
deserializer.deserialize_seq(GlobSetVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use Glob;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{Glob, GlobSet};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_deserialize_borrowed() {
|
||||||
|
let string = r#"{"markdown": "*.md"}"#;
|
||||||
|
|
||||||
|
let map: HashMap<String, Glob> =
|
||||||
|
serde_json::from_str(&string).unwrap();
|
||||||
|
assert_eq!(map["markdown"], Glob::new("*.md").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_deserialize_owned() {
|
||||||
|
let string = r#"{"markdown": "*.md"}"#;
|
||||||
|
|
||||||
|
let v: serde_json::Value = serde_json::from_str(&string).unwrap();
|
||||||
|
let map: HashMap<String, Glob> = serde_json::from_value(v).unwrap();
|
||||||
|
assert_eq!(map["markdown"], Glob::new("*.md").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_deserialize_error() {
|
||||||
|
let string = r#"{"error": "["}"#;
|
||||||
|
|
||||||
|
let map = serde_json::from_str::<HashMap<String, Glob>>(&string);
|
||||||
|
|
||||||
|
assert!(map.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn glob_json_works() {
|
fn glob_json_works() {
|
||||||
@ -35,4 +117,12 @@ mod tests {
|
|||||||
let de: Glob = serde_json::from_str(&ser).unwrap();
|
let de: Glob = serde_json::from_str(&ser).unwrap();
|
||||||
assert_eq!(test_glob, de);
|
assert_eq!(test_glob, de);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_set_deserialize() {
|
||||||
|
let j = r#" ["src/**/*.rs", "README.md"] "#;
|
||||||
|
let set: GlobSet = serde_json::from_str(j).unwrap();
|
||||||
|
assert!(set.is_match("src/lib.rs"));
|
||||||
|
assert!(!set.is_match("Cargo.lock"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep"
|
name = "grep"
|
||||||
version = "0.2.8" #:version
|
version = "0.3.2" #: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.
|
||||||
@ -10,24 +10,24 @@ homepage = "https://github.com/BurntSushi/ripgrep/tree/master/crates/grep"
|
|||||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/grep"
|
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/grep"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
||||||
license = "Unlicense/MIT"
|
license = "Unlicense OR MIT"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
grep-cli = { version = "0.1.6", path = "../cli" }
|
grep-cli = { version = "0.1.11", path = "../cli" }
|
||||||
grep-matcher = { version = "0.1.5", path = "../matcher" }
|
grep-matcher = { version = "0.1.7", path = "../matcher" }
|
||||||
grep-pcre2 = { version = "0.1.5", path = "../pcre2", optional = true }
|
grep-pcre2 = { version = "0.1.8", path = "../pcre2", optional = true }
|
||||||
grep-printer = { version = "0.1.6", path = "../printer" }
|
grep-printer = { version = "0.2.2", path = "../printer" }
|
||||||
grep-regex = { version = "0.1.9", path = "../regex" }
|
grep-regex = { version = "0.1.13", path = "../regex" }
|
||||||
grep-searcher = { version = "0.1.8", path = "../searcher" }
|
grep-searcher = { version = "0.1.14", path = "../searcher" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
termcolor = "1.0.4"
|
termcolor = "1.0.4"
|
||||||
walkdir = "2.2.7"
|
walkdir = "2.2.7"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd-accel = ["grep-searcher/simd-accel"]
|
|
||||||
pcre2 = ["grep-pcre2"]
|
pcre2 = ["grep-pcre2"]
|
||||||
|
|
||||||
# This feature is DEPRECATED. Runtime dispatch is used for SIMD now.
|
# These features are DEPRECATED. Runtime dispatch is used for SIMD now.
|
||||||
|
simd-accel = []
|
||||||
avx-accel = []
|
avx-accel = []
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
use std::env;
|
use std::{env, error::Error, ffi::OsString, io::IsTerminal, process};
|
||||||
use std::error::Error;
|
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use grep::cli;
|
use {
|
||||||
use grep::printer::{ColorSpecs, StandardBuilder};
|
grep::{
|
||||||
use grep::regex::RegexMatcher;
|
cli,
|
||||||
use grep::searcher::{BinaryDetection, SearcherBuilder};
|
printer::{ColorSpecs, StandardBuilder},
|
||||||
use termcolor::ColorChoice;
|
regex::RegexMatcher,
|
||||||
use walkdir::WalkDir;
|
searcher::{BinaryDetection, SearcherBuilder},
|
||||||
|
},
|
||||||
|
termcolor::ColorChoice,
|
||||||
|
walkdir::WalkDir,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(err) = try_main() {
|
if let Err(err) = try_main() {
|
||||||
@ -36,7 +37,7 @@ fn search(pattern: &str, paths: &[OsString]) -> Result<(), Box<dyn Error>> {
|
|||||||
.build();
|
.build();
|
||||||
let mut printer = StandardBuilder::new()
|
let mut printer = StandardBuilder::new()
|
||||||
.color_specs(ColorSpecs::default_with_color())
|
.color_specs(ColorSpecs::default_with_color())
|
||||||
.build(cli::stdout(if cli::is_tty_stdout() {
|
.build(cli::stdout(if std::io::stdout().is_terminal() {
|
||||||
ColorChoice::Auto
|
ColorChoice::Auto
|
||||||
} else {
|
} else {
|
||||||
ColorChoice::Never
|
ColorChoice::Never
|
||||||
|
@ -12,8 +12,6 @@ are sparse.
|
|||||||
A cookbook and a guide are planned.
|
A cookbook and a guide are planned.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
pub extern crate grep_cli as cli;
|
pub extern crate grep_cli as cli;
|
||||||
pub extern crate grep_matcher as matcher;
|
pub extern crate grep_matcher as matcher;
|
||||||
#[cfg(feature = "pcre2")]
|
#[cfg(feature = "pcre2")]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.4.18" #:version
|
version = "0.4.23" #: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`
|
||||||
@ -11,29 +11,34 @@ homepage = "https://github.com/BurntSushi/ripgrep/tree/master/crates/ignore"
|
|||||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/ignore"
|
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/ignore"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["glob", "ignore", "gitignore", "pattern", "file"]
|
keywords = ["glob", "ignore", "gitignore", "pattern", "file"]
|
||||||
license = "Unlicense/MIT"
|
license = "Unlicense OR MIT"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-utils = "0.8.0"
|
crossbeam-deque = "0.8.3"
|
||||||
globset = { version = "0.4.7", path = "../globset" }
|
globset = { version = "0.4.15", path = "../globset" }
|
||||||
lazy_static = "1.1"
|
log = "0.4.20"
|
||||||
log = "0.4.5"
|
memchr = "2.6.3"
|
||||||
memchr = "2.1"
|
same-file = "1.0.6"
|
||||||
regex = "1.1"
|
walkdir = "2.4.0"
|
||||||
same-file = "1.0.4"
|
|
||||||
thread_local = "1"
|
[dependencies.regex-automata]
|
||||||
walkdir = "2.2.7"
|
version = "0.4.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["std", "perf", "syntax", "meta", "nfa", "hybrid", "dfa-onepass"]
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.winapi-util]
|
[target.'cfg(windows)'.dependencies.winapi-util]
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
crossbeam-channel = "0.5.0"
|
bstr = { version = "1.6.2", default-features = false, features = ["std"] }
|
||||||
|
crossbeam-channel = "0.5.15"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd-accel = ["globset/simd-accel"]
|
# DEPRECATED. It is a no-op. SIMD is done automatically through runtime
|
||||||
|
# dispatch.
|
||||||
|
simd-accel = []
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
use std::env;
|
use std::{env, io::Write, path::Path};
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use ignore::WalkBuilder;
|
use {bstr::ByteVec, ignore::WalkBuilder, walkdir::WalkDir};
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut path = env::args().nth(1).unwrap();
|
let mut path = env::args().nth(1).unwrap();
|
||||||
@ -19,10 +15,11 @@ fn main() {
|
|||||||
simple = true;
|
simple = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stdout_thread = thread::spawn(move || {
|
let stdout_thread = std::thread::spawn(move || {
|
||||||
let mut stdout = io::BufWriter::new(io::stdout());
|
let mut stdout = std::io::BufWriter::new(std::io::stdout());
|
||||||
for dent in rx {
|
for dent in rx {
|
||||||
write_path(&mut stdout, dent.path());
|
stdout.write(&*Vec::from_path_lossy(dent.path())).unwrap();
|
||||||
|
stdout.write(b"\n").unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -65,16 +62,3 @@ impl DirEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn write_path<W: Write>(mut wtr: W, path: &Path) {
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
|
||||||
wtr.write(path.as_os_str().as_bytes()).unwrap();
|
|
||||||
wtr.write(b"\n").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
fn write_path<W: Write>(mut wtr: W, path: &Path) {
|
|
||||||
wtr.write(path.to_string_lossy().as_bytes()).unwrap();
|
|
||||||
wtr.write(b"\n").unwrap();
|
|
||||||
}
|
|
||||||
|
@ -9,91 +9,118 @@
|
|||||||
/// Please try to keep this list sorted lexicographically and wrapped to 79
|
/// Please try to keep this list sorted lexicographically and wrapped to 79
|
||||||
/// columns (inclusive).
|
/// columns (inclusive).
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||||
("agda", &["*.agda", "*.lagda"]),
|
(&["ada"], &["*.adb", "*.ads"]),
|
||||||
("aidl", &["*.aidl"]),
|
(&["agda"], &["*.agda", "*.lagda"]),
|
||||||
("amake", &["*.mk", "*.bp"]),
|
(&["aidl"], &["*.aidl"]),
|
||||||
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
|
(&["alire"], &["alire.toml"]),
|
||||||
("asm", &["*.asm", "*.s", "*.S"]),
|
(&["amake"], &["*.mk", "*.bp"]),
|
||||||
("asp", &[
|
(&["asciidoc"], &["*.adoc", "*.asc", "*.asciidoc"]),
|
||||||
"*.aspx", "*.aspx.cs", "*.aspx.vb", "*.ascx", "*.ascx.cs", "*.ascx.vb",
|
(&["asm"], &["*.asm", "*.s", "*.S"]),
|
||||||
|
(&["asp"], &[
|
||||||
|
"*.aspx", "*.aspx.cs", "*.aspx.vb", "*.ascx", "*.ascx.cs",
|
||||||
|
"*.ascx.vb", "*.asp"
|
||||||
]),
|
]),
|
||||||
("ats", &["*.ats", "*.dats", "*.sats", "*.hats"]),
|
(&["ats"], &["*.ats", "*.dats", "*.sats", "*.hats"]),
|
||||||
("avro", &["*.avdl", "*.avpr", "*.avsc"]),
|
(&["avro"], &["*.avdl", "*.avpr", "*.avsc"]),
|
||||||
("awk", &["*.awk"]),
|
(&["awk"], &["*.awk"]),
|
||||||
("bazel", &["*.bazel", "*.bzl", "*.BUILD", "*.bazelrc", "BUILD", "WORKSPACE"]),
|
(&["bat", "batch"], &["*.bat"]),
|
||||||
("bitbake", &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
(&["bazel"], &[
|
||||||
("brotli", &["*.br"]),
|
"*.bazel", "*.bzl", "*.BUILD", "*.bazelrc", "BUILD", "MODULE.bazel",
|
||||||
("buildstream", &["*.bst"]),
|
"WORKSPACE", "WORKSPACE.bazel",
|
||||||
("bzip2", &["*.bz2", "*.tbz2"]),
|
]),
|
||||||
("c", &["*.[chH]", "*.[chH].in", "*.cats"]),
|
(&["bitbake"], &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
||||||
("cabal", &["*.cabal"]),
|
(&["brotli"], &["*.br"]),
|
||||||
("cbor", &["*.cbor"]),
|
(&["buildstream"], &["*.bst"]),
|
||||||
("ceylon", &["*.ceylon"]),
|
(&["bzip2"], &["*.bz2", "*.tbz2"]),
|
||||||
("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
|
(&["c"], &["*.[chH]", "*.[chH].in", "*.cats"]),
|
||||||
("cmake", &["*.cmake", "CMakeLists.txt"]),
|
(&["cabal"], &["*.cabal"]),
|
||||||
("coffeescript", &["*.coffee"]),
|
(&["candid"], &["*.did"]),
|
||||||
("config", &["*.cfg", "*.conf", "*.config", "*.ini"]),
|
(&["carp"], &["*.carp"]),
|
||||||
("coq", &["*.v"]),
|
(&["cbor"], &["*.cbor"]),
|
||||||
("cpp", &[
|
(&["ceylon"], &["*.ceylon"]),
|
||||||
|
(&["clojure"], &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
|
||||||
|
(&["cmake"], &["*.cmake", "CMakeLists.txt"]),
|
||||||
|
(&["cmd"], &["*.bat", "*.cmd"]),
|
||||||
|
(&["cml"], &["*.cml"]),
|
||||||
|
(&["coffeescript"], &["*.coffee"]),
|
||||||
|
(&["config"], &["*.cfg", "*.conf", "*.config", "*.ini"]),
|
||||||
|
(&["coq"], &["*.v"]),
|
||||||
|
(&["cpp"], &[
|
||||||
"*.[ChH]", "*.cc", "*.[ch]pp", "*.[ch]xx", "*.hh", "*.inl",
|
"*.[ChH]", "*.cc", "*.[ch]pp", "*.[ch]xx", "*.hh", "*.inl",
|
||||||
"*.[ChH].in", "*.cc.in", "*.[ch]pp.in", "*.[ch]xx.in", "*.hh.in",
|
"*.[ChH].in", "*.cc.in", "*.[ch]pp.in", "*.[ch]xx.in", "*.hh.in",
|
||||||
]),
|
]),
|
||||||
("creole", &["*.creole"]),
|
(&["creole"], &["*.creole"]),
|
||||||
("crystal", &["Projectfile", "*.cr"]),
|
(&["crystal"], &["Projectfile", "*.cr", "*.ecr", "shard.yml"]),
|
||||||
("cs", &["*.cs"]),
|
(&["cs"], &["*.cs"]),
|
||||||
("csharp", &["*.cs"]),
|
(&["csharp"], &["*.cs"]),
|
||||||
("cshtml", &["*.cshtml"]),
|
(&["cshtml"], &["*.cshtml"]),
|
||||||
("css", &["*.css", "*.scss"]),
|
(&["csproj"], &["*.csproj"]),
|
||||||
("csv", &["*.csv"]),
|
(&["css"], &["*.css", "*.scss"]),
|
||||||
("cython", &["*.pyx", "*.pxi", "*.pxd"]),
|
(&["csv"], &["*.csv"]),
|
||||||
("d", &["*.d"]),
|
(&["cuda"], &["*.cu", "*.cuh"]),
|
||||||
("dart", &["*.dart"]),
|
(&["cython"], &["*.pyx", "*.pxi", "*.pxd"]),
|
||||||
("dhall", &["*.dhall"]),
|
(&["d"], &["*.d"]),
|
||||||
("diff", &["*.patch", "*.diff"]),
|
(&["dart"], &["*.dart"]),
|
||||||
("docker", &["*Dockerfile*"]),
|
(&["devicetree"], &["*.dts", "*.dtsi"]),
|
||||||
("dvc", &["Dvcfile", "*.dvc"]),
|
(&["dhall"], &["*.dhall"]),
|
||||||
("ebuild", &["*.ebuild"]),
|
(&["diff"], &["*.patch", "*.diff"]),
|
||||||
("edn", &["*.edn"]),
|
(&["dita"], &["*.dita", "*.ditamap", "*.ditaval"]),
|
||||||
("elisp", &["*.el"]),
|
(&["docker"], &["*Dockerfile*"]),
|
||||||
("elixir", &["*.ex", "*.eex", "*.exs"]),
|
(&["dockercompose"], &["docker-compose.yml", "docker-compose.*.yml"]),
|
||||||
("elm", &["*.elm"]),
|
(&["dts"], &["*.dts", "*.dtsi"]),
|
||||||
("erb", &["*.erb"]),
|
(&["dvc"], &["Dvcfile", "*.dvc"]),
|
||||||
("erlang", &["*.erl", "*.hrl"]),
|
(&["ebuild"], &["*.ebuild", "*.eclass"]),
|
||||||
("fidl", &["*.fidl"]),
|
(&["edn"], &["*.edn"]),
|
||||||
("fish", &["*.fish"]),
|
(&["elisp"], &["*.el"]),
|
||||||
("flatbuffers", &["*.fbs"]),
|
(&["elixir"], &["*.ex", "*.eex", "*.exs", "*.heex", "*.leex", "*.livemd"]),
|
||||||
("fortran", &[
|
(&["elm"], &["*.elm"]),
|
||||||
|
(&["erb"], &["*.erb"]),
|
||||||
|
(&["erlang"], &["*.erl", "*.hrl"]),
|
||||||
|
(&["fennel"], &["*.fnl"]),
|
||||||
|
(&["fidl"], &["*.fidl"]),
|
||||||
|
(&["fish"], &["*.fish"]),
|
||||||
|
(&["flatbuffers"], &["*.fbs"]),
|
||||||
|
(&["fortran"], &[
|
||||||
"*.f", "*.F", "*.f77", "*.F77", "*.pfo",
|
"*.f", "*.F", "*.f77", "*.F77", "*.pfo",
|
||||||
"*.f90", "*.F90", "*.f95", "*.F95",
|
"*.f90", "*.F90", "*.f95", "*.F95",
|
||||||
]),
|
]),
|
||||||
("fsharp", &["*.fs", "*.fsx", "*.fsi"]),
|
(&["fsharp"], &["*.fs", "*.fsx", "*.fsi"]),
|
||||||
("fut", &[".fut"]),
|
(&["fut"], &["*.fut"]),
|
||||||
("gap", &["*.g", "*.gap", "*.gi", "*.gd", "*.tst"]),
|
(&["gap"], &["*.g", "*.gap", "*.gi", "*.gd", "*.tst"]),
|
||||||
("gn", &["*.gn", "*.gni"]),
|
(&["gn"], &["*.gn", "*.gni"]),
|
||||||
("go", &["*.go"]),
|
(&["go"], &["*.go"]),
|
||||||
("gradle", &["*.gradle"]),
|
(&["gprbuild"], &["*.gpr"]),
|
||||||
("groovy", &["*.groovy", "*.gradle"]),
|
(&["gradle"], &[
|
||||||
("gzip", &["*.gz", "*.tgz"]),
|
"*.gradle", "*.gradle.kts", "gradle.properties", "gradle-wrapper.*",
|
||||||
("h", &["*.h", "*.hpp"]),
|
"gradlew", "gradlew.bat",
|
||||||
("haml", &["*.haml"]),
|
]),
|
||||||
("haskell", &["*.hs", "*.lhs", "*.cpphs", "*.c2hs", "*.hsc"]),
|
(&["graphql"], &["*.graphql", "*.graphqls"]),
|
||||||
("hbs", &["*.hbs"]),
|
(&["groovy"], &["*.groovy", "*.gradle"]),
|
||||||
("hs", &["*.hs", "*.lhs"]),
|
(&["gzip"], &["*.gz", "*.tgz"]),
|
||||||
("html", &["*.htm", "*.html", "*.ejs"]),
|
(&["h"], &["*.h", "*.hh", "*.hpp"]),
|
||||||
("idris", &["*.idr", "*.lidr"]),
|
(&["haml"], &["*.haml"]),
|
||||||
("java", &["*.java", "*.jsp", "*.jspx", "*.properties"]),
|
(&["hare"], &["*.ha"]),
|
||||||
("jinja", &["*.j2", "*.jinja", "*.jinja2"]),
|
(&["haskell"], &["*.hs", "*.lhs", "*.cpphs", "*.c2hs", "*.hsc"]),
|
||||||
("jl", &["*.jl"]),
|
(&["hbs"], &["*.hbs"]),
|
||||||
("js", &["*.js", "*.jsx", "*.vue"]),
|
(&["hs"], &["*.hs", "*.lhs"]),
|
||||||
("json", &["*.json", "composer.lock"]),
|
(&["html"], &["*.htm", "*.html", "*.ejs"]),
|
||||||
("jsonl", &["*.jsonl"]),
|
(&["hy"], &["*.hy"]),
|
||||||
("julia", &["*.jl"]),
|
(&["idris"], &["*.idr", "*.lidr"]),
|
||||||
("jupyter", &["*.ipynb", "*.jpynb"]),
|
(&["janet"], &["*.janet"]),
|
||||||
("k", &["*.k"]),
|
(&["java"], &["*.java", "*.jsp", "*.jspx", "*.properties"]),
|
||||||
("kotlin", &["*.kt", "*.kts"]),
|
(&["jinja"], &["*.j2", "*.jinja", "*.jinja2"]),
|
||||||
("less", &["*.less"]),
|
(&["jl"], &["*.jl"]),
|
||||||
("license", &[
|
(&["js"], &["*.js", "*.jsx", "*.vue", "*.cjs", "*.mjs"]),
|
||||||
|
(&["json"], &["*.json", "composer.lock", "*.sarif"]),
|
||||||
|
(&["jsonl"], &["*.jsonl"]),
|
||||||
|
(&["julia"], &["*.jl"]),
|
||||||
|
(&["jupyter"], &["*.ipynb", "*.jpynb"]),
|
||||||
|
(&["k"], &["*.k"]),
|
||||||
|
(&["kotlin"], &["*.kt", "*.kts"]),
|
||||||
|
(&["lean"], &["*.lean"]),
|
||||||
|
(&["less"], &["*.less"]),
|
||||||
|
(&["license"], &[
|
||||||
// General
|
// General
|
||||||
"COPYING", "COPYING[.-]*",
|
"COPYING", "COPYING[.-]*",
|
||||||
"COPYRIGHT", "COPYRIGHT[.-]*",
|
"COPYRIGHT", "COPYRIGHT[.-]*",
|
||||||
@ -120,69 +147,93 @@ pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
|||||||
"MPL-*[0-9]*",
|
"MPL-*[0-9]*",
|
||||||
"OFL-*[0-9]*",
|
"OFL-*[0-9]*",
|
||||||
]),
|
]),
|
||||||
("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
(&["lilypond"], &["*.ly", "*.ily"]),
|
||||||
("lock", &["*.lock", "package-lock.json"]),
|
(&["lisp"], &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
||||||
("log", &["*.log"]),
|
(&["lock"], &["*.lock", "package-lock.json"]),
|
||||||
("lua", &["*.lua"]),
|
(&["log"], &["*.log"]),
|
||||||
("lz4", &["*.lz4"]),
|
(&["lua"], &["*.lua"]),
|
||||||
("lzma", &["*.lzma"]),
|
(&["lz4"], &["*.lz4"]),
|
||||||
("m4", &["*.ac", "*.m4"]),
|
(&["lzma"], &["*.lzma"]),
|
||||||
("make", &[
|
(&["m4"], &["*.ac", "*.m4"]),
|
||||||
|
(&["make"], &[
|
||||||
"[Gg][Nn][Uu]makefile", "[Mm]akefile",
|
"[Gg][Nn][Uu]makefile", "[Mm]akefile",
|
||||||
"[Gg][Nn][Uu]makefile.am", "[Mm]akefile.am",
|
"[Gg][Nn][Uu]makefile.am", "[Mm]akefile.am",
|
||||||
"[Gg][Nn][Uu]makefile.in", "[Mm]akefile.in",
|
"[Gg][Nn][Uu]makefile.in", "[Mm]akefile.in",
|
||||||
"*.mk", "*.mak"
|
"*.mk", "*.mak"
|
||||||
]),
|
]),
|
||||||
("mako", &["*.mako", "*.mao"]),
|
(&["mako"], &["*.mako", "*.mao"]),
|
||||||
("man", &["*.[0-9lnpx]", "*.[0-9][cEFMmpSx]"]),
|
(&["man"], &["*.[0-9lnpx]", "*.[0-9][cEFMmpSx]"]),
|
||||||
("markdown", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
|
(&["markdown", "md"], &[
|
||||||
("matlab", &["*.m"]),
|
"*.markdown",
|
||||||
("md", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
|
"*.md",
|
||||||
("meson", &["meson.build", "meson_options.txt"]),
|
"*.mdown",
|
||||||
("minified", &["*.min.html", "*.min.css", "*.min.js"]),
|
"*.mdwn",
|
||||||
("mint", &["*.mint"]),
|
"*.mkd",
|
||||||
("mk", &["mkfile"]),
|
"*.mkdn",
|
||||||
("ml", &["*.ml"]),
|
"*.mdx",
|
||||||
("msbuild", &[
|
|
||||||
"*.csproj", "*.fsproj", "*.vcxproj", "*.proj", "*.props", "*.targets",
|
|
||||||
]),
|
]),
|
||||||
("nim", &["*.nim", "*.nimf", "*.nimble", "*.nims"]),
|
(&["matlab"], &["*.m"]),
|
||||||
("nix", &["*.nix"]),
|
(&["meson"], &["meson.build", "meson_options.txt", "meson.options"]),
|
||||||
("objc", &["*.h", "*.m"]),
|
(&["minified"], &["*.min.html", "*.min.css", "*.min.js"]),
|
||||||
("objcpp", &["*.h", "*.mm"]),
|
(&["mint"], &["*.mint"]),
|
||||||
("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
(&["mk"], &["mkfile"]),
|
||||||
("org", &["*.org", "*.org_archive"]),
|
(&["ml"], &["*.ml"]),
|
||||||
("pascal", &["*.pas", "*.dpr", "*.lpr", "*.pp", "*.inc"]),
|
(&["motoko"], &["*.mo"]),
|
||||||
("pdf", &["*.pdf"]),
|
(&["msbuild"], &[
|
||||||
("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm", "*.t"]),
|
"*.csproj", "*.fsproj", "*.vcxproj", "*.proj", "*.props", "*.targets",
|
||||||
("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
|
"*.sln",
|
||||||
("po", &["*.po"]),
|
]),
|
||||||
("pod", &["*.pod"]),
|
(&["nim"], &["*.nim", "*.nimf", "*.nimble", "*.nims"]),
|
||||||
("postscript", &["*.eps", "*.ps"]),
|
(&["nix"], &["*.nix"]),
|
||||||
("protobuf", &["*.proto"]),
|
(&["objc"], &["*.h", "*.m"]),
|
||||||
("ps", &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
|
(&["objcpp"], &["*.h", "*.mm"]),
|
||||||
("puppet", &["*.erb", "*.pp", "*.rb"]),
|
(&["ocaml"], &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
||||||
("purs", &["*.purs"]),
|
(&["org"], &["*.org", "*.org_archive"]),
|
||||||
("py", &["*.py"]),
|
(&["pants"], &["BUILD"]),
|
||||||
("qmake", &["*.pro", "*.pri", "*.prf"]),
|
(&["pascal"], &["*.pas", "*.dpr", "*.lpr", "*.pp", "*.inc"]),
|
||||||
("qml", &["*.qml"]),
|
(&["pdf"], &["*.pdf"]),
|
||||||
("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
|
(&["perl"], &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm", "*.t"]),
|
||||||
("racket", &["*.rkt"]),
|
(&["php"], &[
|
||||||
("rdoc", &["*.rdoc"]),
|
// note that PHP 6 doesn't exist
|
||||||
("readme", &["README*", "*README"]),
|
// See: https://wiki.php.net/rfc/php6
|
||||||
("red", &["*.r", "*.red", "*.reds"]),
|
"*.php", "*.php3", "*.php4", "*.php5", "*.php7", "*.php8",
|
||||||
("robot", &["*.robot"]),
|
"*.pht", "*.phtml"
|
||||||
("rst", &["*.rst"]),
|
]),
|
||||||
("ruby", &[
|
(&["po"], &["*.po"]),
|
||||||
|
(&["pod"], &["*.pod"]),
|
||||||
|
(&["postscript"], &["*.eps", "*.ps"]),
|
||||||
|
(&["prolog"], &["*.pl", "*.pro", "*.prolog", "*.P"]),
|
||||||
|
(&["protobuf"], &["*.proto"]),
|
||||||
|
(&["ps"], &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
|
||||||
|
(&["puppet"], &["*.epp", "*.erb", "*.pp", "*.rb"]),
|
||||||
|
(&["purs"], &["*.purs"]),
|
||||||
|
(&["py", "python"], &["*.py", "*.pyi"]),
|
||||||
|
(&["qmake"], &["*.pro", "*.pri", "*.prf"]),
|
||||||
|
(&["qml"], &["*.qml"]),
|
||||||
|
(&["r"], &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
|
||||||
|
(&["racket"], &["*.rkt"]),
|
||||||
|
(&["raku"], &[
|
||||||
|
"*.raku", "*.rakumod", "*.rakudoc", "*.rakutest",
|
||||||
|
"*.p6", "*.pl6", "*.pm6"
|
||||||
|
]),
|
||||||
|
(&["rdoc"], &["*.rdoc"]),
|
||||||
|
(&["readme"], &["README*", "*README"]),
|
||||||
|
(&["reasonml"], &["*.re", "*.rei"]),
|
||||||
|
(&["red"], &["*.r", "*.red", "*.reds"]),
|
||||||
|
(&["rescript"], &["*.res", "*.resi"]),
|
||||||
|
(&["robot"], &["*.robot"]),
|
||||||
|
(&["rst"], &["*.rst"]),
|
||||||
|
(&["ruby"], &[
|
||||||
// Idiomatic files
|
// Idiomatic files
|
||||||
"config.ru", "Gemfile", ".irbrc", "Rakefile",
|
"config.ru", "Gemfile", ".irbrc", "Rakefile",
|
||||||
// Extensions
|
// Extensions
|
||||||
"*.gemspec", "*.rb", "*.rbw"
|
"*.gemspec", "*.rb", "*.rbw"
|
||||||
]),
|
]),
|
||||||
("rust", &["*.rs"]),
|
(&["rust"], &["*.rs"]),
|
||||||
("sass", &["*.sass", "*.scss"]),
|
(&["sass"], &["*.sass", "*.scss"]),
|
||||||
("scala", &["*.scala", "*.sbt"]),
|
(&["scala"], &["*.scala", "*.sbt"]),
|
||||||
("sh", &[
|
(&["seed7"], &["*.sd7", "*.s7i"]),
|
||||||
|
(&["sh"], &[
|
||||||
// Portable/misc. init files
|
// Portable/misc. init files
|
||||||
".login", ".logout", ".profile", "profile",
|
".login", ".logout", ".profile", "profile",
|
||||||
// bash-specific init files
|
// bash-specific init files
|
||||||
@ -205,54 +256,69 @@ pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
|||||||
// Extensions
|
// Extensions
|
||||||
"*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh", "*.zsh",
|
"*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh", "*.zsh",
|
||||||
]),
|
]),
|
||||||
("slim", &["*.skim", "*.slim", "*.slime"]),
|
(&["slim"], &["*.skim", "*.slim", "*.slime"]),
|
||||||
("smarty", &["*.tpl"]),
|
(&["smarty"], &["*.tpl"]),
|
||||||
("sml", &["*.sml", "*.sig"]),
|
(&["sml"], &["*.sml", "*.sig"]),
|
||||||
("soy", &["*.soy"]),
|
(&["solidity"], &["*.sol"]),
|
||||||
("spark", &["*.spark"]),
|
(&["soy"], &["*.soy"]),
|
||||||
("spec", &["*.spec"]),
|
(&["spark"], &["*.spark"]),
|
||||||
("sql", &["*.sql", "*.psql"]),
|
(&["spec"], &["*.spec"]),
|
||||||
("stylus", &["*.styl"]),
|
(&["sql"], &["*.sql", "*.psql"]),
|
||||||
("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
(&["stylus"], &["*.styl"]),
|
||||||
("svg", &["*.svg"]),
|
(&["sv"], &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
||||||
("swift", &["*.swift"]),
|
(&["svelte"], &["*.svelte"]),
|
||||||
("swig", &["*.def", "*.i"]),
|
(&["svg"], &["*.svg"]),
|
||||||
("systemd", &[
|
(&["swift"], &["*.swift"]),
|
||||||
|
(&["swig"], &["*.def", "*.i"]),
|
||||||
|
(&["systemd"], &[
|
||||||
"*.automount", "*.conf", "*.device", "*.link", "*.mount", "*.path",
|
"*.automount", "*.conf", "*.device", "*.link", "*.mount", "*.path",
|
||||||
"*.scope", "*.service", "*.slice", "*.socket", "*.swap", "*.target",
|
"*.scope", "*.service", "*.slice", "*.socket", "*.swap", "*.target",
|
||||||
"*.timer",
|
"*.timer",
|
||||||
]),
|
]),
|
||||||
("taskpaper", &["*.taskpaper"]),
|
(&["taskpaper"], &["*.taskpaper"]),
|
||||||
("tcl", &["*.tcl"]),
|
(&["tcl"], &["*.tcl"]),
|
||||||
("tex", &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib", "*.dtx", "*.ins"]),
|
(&["tex"], &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib", "*.dtx", "*.ins"]),
|
||||||
("textile", &["*.textile"]),
|
(&["texinfo"], &["*.texi"]),
|
||||||
("tf", &["*.tf"]),
|
(&["textile"], &["*.textile"]),
|
||||||
("thrift", &["*.thrift"]),
|
(&["tf"], &[
|
||||||
("toml", &["*.toml", "Cargo.lock"]),
|
"*.tf", "*.auto.tfvars", "terraform.tfvars", "*.tf.json",
|
||||||
("ts", &["*.ts", "*.tsx"]),
|
"*.auto.tfvars.json", "terraform.tfvars.json", "*.terraformrc",
|
||||||
("twig", &["*.twig"]),
|
"terraform.rc", "*.tfrc", "*.terraform.lock.hcl",
|
||||||
("txt", &["*.txt"]),
|
]),
|
||||||
("typoscript", &["*.typoscript", "*.ts"]),
|
(&["thrift"], &["*.thrift"]),
|
||||||
("vala", &["*.vala"]),
|
(&["toml"], &["*.toml", "Cargo.lock"]),
|
||||||
("vb", &["*.vb"]),
|
(&["ts", "typescript"], &["*.ts", "*.tsx", "*.cts", "*.mts"]),
|
||||||
("vcl", &["*.vcl"]),
|
(&["twig"], &["*.twig"]),
|
||||||
("verilog", &["*.v", "*.vh", "*.sv", "*.svh"]),
|
(&["txt"], &["*.txt"]),
|
||||||
("vhdl", &["*.vhd", "*.vhdl"]),
|
(&["typoscript"], &["*.typoscript", "*.ts"]),
|
||||||
("vim", &["*.vim"]),
|
(&["usd"], &["*.usd", "*.usda", "*.usdc"]),
|
||||||
("vimscript", &["*.vim"]),
|
(&["v"], &["*.v", "*.vsh"]),
|
||||||
("webidl", &["*.idl", "*.webidl", "*.widl"]),
|
(&["vala"], &["*.vala"]),
|
||||||
("wiki", &["*.mediawiki", "*.wiki"]),
|
(&["vb"], &["*.vb"]),
|
||||||
("xml", &[
|
(&["vcl"], &["*.vcl"]),
|
||||||
|
(&["verilog"], &["*.v", "*.vh", "*.sv", "*.svh"]),
|
||||||
|
(&["vhdl"], &["*.vhd", "*.vhdl"]),
|
||||||
|
(&["vim"], &[
|
||||||
|
"*.vim", ".vimrc", ".gvimrc", "vimrc", "gvimrc", "_vimrc", "_gvimrc",
|
||||||
|
]),
|
||||||
|
(&["vimscript"], &[
|
||||||
|
"*.vim", ".vimrc", ".gvimrc", "vimrc", "gvimrc", "_vimrc", "_gvimrc",
|
||||||
|
]),
|
||||||
|
(&["vue"], &["*.vue"]),
|
||||||
|
(&["webidl"], &["*.idl", "*.webidl", "*.widl"]),
|
||||||
|
(&["wgsl"], &["*.wgsl"]),
|
||||||
|
(&["wiki"], &["*.mediawiki", "*.wiki"]),
|
||||||
|
(&["xml"], &[
|
||||||
"*.xml", "*.xml.dist", "*.dtd", "*.xsl", "*.xslt", "*.xsd", "*.xjb",
|
"*.xml", "*.xml.dist", "*.dtd", "*.xsl", "*.xslt", "*.xsd", "*.xjb",
|
||||||
"*.rng", "*.sch", "*.xhtml",
|
"*.rng", "*.sch", "*.xhtml",
|
||||||
]),
|
]),
|
||||||
("xz", &["*.xz", "*.txz"]),
|
(&["xz"], &["*.xz", "*.txz"]),
|
||||||
("yacc", &["*.y"]),
|
(&["yacc"], &["*.y"]),
|
||||||
("yaml", &["*.yaml", "*.yml"]),
|
(&["yaml"], &["*.yaml", "*.yml"]),
|
||||||
("yang", &["*.yang"]),
|
(&["yang"], &["*.yang"]),
|
||||||
("z", &["*.Z"]),
|
(&["z"], &["*.Z"]),
|
||||||
("zig", &["*.zig"]),
|
(&["zig"], &["*.zig"]),
|
||||||
("zsh", &[
|
(&["zsh"], &[
|
||||||
".zshenv", "zshenv",
|
".zshenv", "zshenv",
|
||||||
".zlogin", "zlogin",
|
".zlogin", "zlogin",
|
||||||
".zlogout", "zlogout",
|
".zlogout", "zlogout",
|
||||||
@ -260,5 +326,27 @@ pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
|||||||
".zshrc", "zshrc",
|
".zshrc", "zshrc",
|
||||||
"*.zsh",
|
"*.zsh",
|
||||||
]),
|
]),
|
||||||
("zstd", &["*.zst", "*.zstd"]),
|
(&["zstd"], &["*.zst", "*.zstd"]),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::DEFAULT_TYPES;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_types_are_sorted() {
|
||||||
|
let mut names = DEFAULT_TYPES.iter().map(|(aliases, _)| aliases[0]);
|
||||||
|
let Some(mut previous_name) = names.next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for name in names {
|
||||||
|
assert!(
|
||||||
|
name > previous_name,
|
||||||
|
r#""{}" should be sorted before "{}" in `DEFAULT_TYPES`"#,
|
||||||
|
name,
|
||||||
|
previous_name
|
||||||
|
);
|
||||||
|
previous_name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,28 +13,34 @@
|
|||||||
// with non-obvious failure modes. Alas, such things haven't been documented
|
// with non-obvious failure modes. Alas, such things haven't been documented
|
||||||
// well.
|
// well.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
use std::ffi::{OsStr, OsString};
|
collections::HashMap,
|
||||||
use std::fs::{File, FileType};
|
ffi::{OsStr, OsString},
|
||||||
use std::io::{self, BufRead};
|
fs::{File, FileType},
|
||||||
use std::path::{Path, PathBuf};
|
io::{self, BufRead},
|
||||||
use std::sync::{Arc, RwLock};
|
path::{Path, PathBuf},
|
||||||
|
sync::{Arc, RwLock, Weak},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::gitignore::{self, Gitignore, GitignoreBuilder};
|
use crate::{
|
||||||
use crate::overrides::{self, Override};
|
gitignore::{self, Gitignore, GitignoreBuilder},
|
||||||
use crate::pathutil::{is_hidden, strip_prefix};
|
overrides::{self, Override},
|
||||||
use crate::types::{self, Types};
|
pathutil::{is_hidden, strip_prefix},
|
||||||
use crate::walk::DirEntry;
|
types::{self, Types},
|
||||||
use crate::{Error, Match, PartialErrorBuilder};
|
walk::DirEntry,
|
||||||
|
{Error, Match, PartialErrorBuilder},
|
||||||
|
};
|
||||||
|
|
||||||
/// IgnoreMatch represents information about where a match came from when using
|
/// IgnoreMatch represents information about where a match came from when using
|
||||||
/// the `Ignore` matcher.
|
/// the `Ignore` matcher.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IgnoreMatch<'a>(IgnoreMatchInner<'a>);
|
#[allow(dead_code)]
|
||||||
|
pub(crate) struct IgnoreMatch<'a>(IgnoreMatchInner<'a>);
|
||||||
|
|
||||||
/// IgnoreMatchInner describes precisely where the match information came from.
|
/// IgnoreMatchInner describes precisely where the match information came from.
|
||||||
/// This is private to allow expansion to more matchers in the future.
|
/// This is private to allow expansion to more matchers in the future.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(dead_code)]
|
||||||
enum IgnoreMatchInner<'a> {
|
enum IgnoreMatchInner<'a> {
|
||||||
Override(overrides::Glob<'a>),
|
Override(overrides::Glob<'a>),
|
||||||
Gitignore(&'a gitignore::Glob),
|
Gitignore(&'a gitignore::Glob),
|
||||||
@ -85,7 +91,7 @@ struct IgnoreOptions {
|
|||||||
|
|
||||||
/// Ignore is a matcher useful for recursively walking one or more directories.
|
/// Ignore is a matcher useful for recursively walking one or more directories.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Ignore(Arc<IgnoreInner>);
|
pub(crate) struct Ignore(Arc<IgnoreInner>);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct IgnoreInner {
|
struct IgnoreInner {
|
||||||
@ -95,7 +101,7 @@ struct IgnoreInner {
|
|||||||
/// Note that this is never used during matching, only when adding new
|
/// Note that this is never used during matching, only when adding new
|
||||||
/// parent directory matchers. This avoids needing to rebuild glob sets for
|
/// parent directory matchers. This avoids needing to rebuild glob sets for
|
||||||
/// parent directories if many paths are being searched.
|
/// parent directories if many paths are being searched.
|
||||||
compiled: Arc<RwLock<HashMap<OsString, Ignore>>>,
|
compiled: Arc<RwLock<HashMap<OsString, Weak<IgnoreInner>>>>,
|
||||||
/// The path to the directory that this matcher was built from.
|
/// The path to the directory that this matcher was built from.
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
/// An override matcher (default is empty).
|
/// An override matcher (default is empty).
|
||||||
@ -134,22 +140,22 @@ struct IgnoreInner {
|
|||||||
|
|
||||||
impl Ignore {
|
impl Ignore {
|
||||||
/// Return the directory path of this matcher.
|
/// Return the directory path of this matcher.
|
||||||
pub fn path(&self) -> &Path {
|
pub(crate) fn path(&self) -> &Path {
|
||||||
&self.0.dir
|
&self.0.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if this matcher has no parent.
|
/// Return true if this matcher has no parent.
|
||||||
pub fn is_root(&self) -> bool {
|
pub(crate) fn is_root(&self) -> bool {
|
||||||
self.0.parent.is_none()
|
self.0.parent.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this matcher was added via the `add_parents` method.
|
/// Returns true if this matcher was added via the `add_parents` method.
|
||||||
pub fn is_absolute_parent(&self) -> bool {
|
pub(crate) fn is_absolute_parent(&self) -> bool {
|
||||||
self.0.is_absolute_parent
|
self.0.is_absolute_parent
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return this matcher's parent, if one exists.
|
/// Return this matcher's parent, if one exists.
|
||||||
pub fn parent(&self) -> Option<Ignore> {
|
pub(crate) fn parent(&self) -> Option<Ignore> {
|
||||||
self.0.parent.clone()
|
self.0.parent.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +163,7 @@ impl Ignore {
|
|||||||
///
|
///
|
||||||
/// Note that this can only be called on an `Ignore` matcher with no
|
/// Note that this can only be called on an `Ignore` matcher with no
|
||||||
/// parents (i.e., `is_root` returns `true`). This will panic otherwise.
|
/// parents (i.e., `is_root` returns `true`). This will panic otherwise.
|
||||||
pub fn add_parents<P: AsRef<Path>>(
|
pub(crate) fn add_parents<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
) -> (Ignore, Option<Error>) {
|
) -> (Ignore, Option<Error>) {
|
||||||
@ -194,21 +200,28 @@ impl Ignore {
|
|||||||
let mut ig = self.clone();
|
let mut ig = self.clone();
|
||||||
for parent in parents.into_iter().rev() {
|
for parent in parents.into_iter().rev() {
|
||||||
let mut compiled = self.0.compiled.write().unwrap();
|
let mut compiled = self.0.compiled.write().unwrap();
|
||||||
if let Some(prebuilt) = compiled.get(parent.as_os_str()) {
|
if let Some(weak) = compiled.get(parent.as_os_str()) {
|
||||||
ig = prebuilt.clone();
|
if let Some(prebuilt) = weak.upgrade() {
|
||||||
continue;
|
ig = Ignore(prebuilt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let (mut igtmp, err) = ig.add_child_path(parent);
|
let (mut igtmp, err) = ig.add_child_path(parent);
|
||||||
errs.maybe_push(err);
|
errs.maybe_push(err);
|
||||||
igtmp.is_absolute_parent = true;
|
igtmp.is_absolute_parent = true;
|
||||||
igtmp.absolute_base = Some(absolute_base.clone());
|
igtmp.absolute_base = Some(absolute_base.clone());
|
||||||
igtmp.has_git = if self.0.opts.git_ignore {
|
igtmp.has_git =
|
||||||
parent.join(".git").exists()
|
if self.0.opts.require_git && self.0.opts.git_ignore {
|
||||||
} else {
|
parent.join(".git").exists()
|
||||||
false
|
} else {
|
||||||
};
|
false
|
||||||
ig = Ignore(Arc::new(igtmp));
|
};
|
||||||
compiled.insert(parent.as_os_str().to_os_string(), ig.clone());
|
let ig_arc = Arc::new(igtmp);
|
||||||
|
ig = Ignore(ig_arc.clone());
|
||||||
|
compiled.insert(
|
||||||
|
parent.as_os_str().to_os_string(),
|
||||||
|
Arc::downgrade(&ig_arc),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(ig, errs.into_error_option())
|
(ig, errs.into_error_option())
|
||||||
}
|
}
|
||||||
@ -221,7 +234,7 @@ impl Ignore {
|
|||||||
/// returned if it exists.
|
/// returned if it exists.
|
||||||
///
|
///
|
||||||
/// Note that all I/O errors are completely ignored.
|
/// Note that all I/O errors are completely ignored.
|
||||||
pub fn add_child<P: AsRef<Path>>(
|
pub(crate) fn add_child<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
dir: P,
|
dir: P,
|
||||||
) -> (Ignore, Option<Error>) {
|
) -> (Ignore, Option<Error>) {
|
||||||
@ -231,7 +244,9 @@ impl Ignore {
|
|||||||
|
|
||||||
/// Like add_child, but takes a full path and returns an IgnoreInner.
|
/// Like add_child, but takes a full path and returns an IgnoreInner.
|
||||||
fn add_child_path(&self, dir: &Path) -> (IgnoreInner, Option<Error>) {
|
fn add_child_path(&self, dir: &Path) -> (IgnoreInner, Option<Error>) {
|
||||||
let git_type = if self.0.opts.git_ignore || self.0.opts.git_exclude {
|
let git_type = if self.0.opts.require_git
|
||||||
|
&& (self.0.opts.git_ignore || self.0.opts.git_exclude)
|
||||||
|
{
|
||||||
dir.join(".git").metadata().ok().map(|md| md.file_type())
|
dir.join(".git").metadata().ok().map(|md| md.file_type())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -332,7 +347,7 @@ impl Ignore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Like `matched`, but works with a directory entry instead.
|
/// Like `matched`, but works with a directory entry instead.
|
||||||
pub fn matched_dir_entry<'a>(
|
pub(crate) fn matched_dir_entry<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
dent: &DirEntry,
|
dent: &DirEntry,
|
||||||
) -> Match<IgnoreMatch<'a>> {
|
) -> Match<IgnoreMatch<'a>> {
|
||||||
@ -439,7 +454,29 @@ impl Ignore {
|
|||||||
}
|
}
|
||||||
if self.0.opts.parents {
|
if self.0.opts.parents {
|
||||||
if let Some(abs_parent_path) = self.absolute_base() {
|
if let Some(abs_parent_path) = self.absolute_base() {
|
||||||
let path = abs_parent_path.join(path);
|
// What we want to do here is take the absolute base path of
|
||||||
|
// this directory and join it with the path we're searching.
|
||||||
|
// The main issue we want to avoid is accidentally duplicating
|
||||||
|
// directory components, so we try to strip any common prefix
|
||||||
|
// off of `path`. Overall, this seems a little ham-fisted, but
|
||||||
|
// it does fix a nasty bug. It should do fine until we overhaul
|
||||||
|
// this crate.
|
||||||
|
let dirpath = self.0.dir.as_path();
|
||||||
|
let path_prefix = match strip_prefix("./", dirpath) {
|
||||||
|
None => dirpath,
|
||||||
|
Some(stripped_dot_slash) => stripped_dot_slash,
|
||||||
|
};
|
||||||
|
let path = match strip_prefix(path_prefix, path) {
|
||||||
|
None => abs_parent_path.join(path),
|
||||||
|
Some(p) => {
|
||||||
|
let p = match strip_prefix("/", p) {
|
||||||
|
None => p,
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
abs_parent_path.join(p)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for ig in
|
for ig in
|
||||||
self.parents().skip_while(|ig| !ig.0.is_absolute_parent)
|
self.parents().skip_while(|ig| !ig.0.is_absolute_parent)
|
||||||
{
|
{
|
||||||
@ -495,7 +532,7 @@ impl Ignore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over parent ignore matchers, including this one.
|
/// Returns an iterator over parent ignore matchers, including this one.
|
||||||
pub fn parents(&self) -> Parents<'_> {
|
pub(crate) fn parents(&self) -> Parents<'_> {
|
||||||
Parents(Some(self))
|
Parents(Some(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,7 +546,7 @@ impl Ignore {
|
|||||||
/// An iterator over all parents of an ignore matcher, including itself.
|
/// An iterator over all parents of an ignore matcher, including itself.
|
||||||
///
|
///
|
||||||
/// The lifetime `'a` refers to the lifetime of the initial `Ignore` matcher.
|
/// The lifetime `'a` refers to the lifetime of the initial `Ignore` matcher.
|
||||||
pub struct Parents<'a>(Option<&'a Ignore>);
|
pub(crate) struct Parents<'a>(Option<&'a Ignore>);
|
||||||
|
|
||||||
impl<'a> Iterator for Parents<'a> {
|
impl<'a> Iterator for Parents<'a> {
|
||||||
type Item = &'a Ignore;
|
type Item = &'a Ignore;
|
||||||
@ -527,7 +564,7 @@ impl<'a> Iterator for Parents<'a> {
|
|||||||
|
|
||||||
/// A builder for creating an Ignore matcher.
|
/// A builder for creating an Ignore matcher.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IgnoreBuilder {
|
pub(crate) struct IgnoreBuilder {
|
||||||
/// The root directory path for this ignore matcher.
|
/// The root directory path for this ignore matcher.
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
/// An override matcher (default is empty).
|
/// An override matcher (default is empty).
|
||||||
@ -547,7 +584,7 @@ impl IgnoreBuilder {
|
|||||||
///
|
///
|
||||||
/// All relative file paths are resolved with respect to the current
|
/// All relative file paths are resolved with respect to the current
|
||||||
/// working directory.
|
/// working directory.
|
||||||
pub fn new() -> IgnoreBuilder {
|
pub(crate) fn new() -> IgnoreBuilder {
|
||||||
IgnoreBuilder {
|
IgnoreBuilder {
|
||||||
dir: Path::new("").to_path_buf(),
|
dir: Path::new("").to_path_buf(),
|
||||||
overrides: Arc::new(Override::empty()),
|
overrides: Arc::new(Override::empty()),
|
||||||
@ -571,7 +608,7 @@ impl IgnoreBuilder {
|
|||||||
///
|
///
|
||||||
/// The matcher returned won't match anything until ignore rules from
|
/// The matcher returned won't match anything until ignore rules from
|
||||||
/// directories are added to it.
|
/// directories are added to it.
|
||||||
pub fn build(&self) -> Ignore {
|
pub(crate) fn build(&self) -> Ignore {
|
||||||
let git_global_matcher = if !self.opts.git_global {
|
let git_global_matcher = if !self.opts.git_global {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} else {
|
||||||
@ -613,7 +650,10 @@ impl IgnoreBuilder {
|
|||||||
/// By default, no override matcher is used.
|
/// By default, no override matcher is used.
|
||||||
///
|
///
|
||||||
/// This overrides any previous setting.
|
/// This overrides any previous setting.
|
||||||
pub fn overrides(&mut self, overrides: Override) -> &mut IgnoreBuilder {
|
pub(crate) fn overrides(
|
||||||
|
&mut self,
|
||||||
|
overrides: Override,
|
||||||
|
) -> &mut IgnoreBuilder {
|
||||||
self.overrides = Arc::new(overrides);
|
self.overrides = Arc::new(overrides);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -623,13 +663,13 @@ impl IgnoreBuilder {
|
|||||||
/// By default, no file type matcher is used.
|
/// By default, no file type matcher is used.
|
||||||
///
|
///
|
||||||
/// This overrides any previous setting.
|
/// This overrides any previous setting.
|
||||||
pub fn types(&mut self, types: Types) -> &mut IgnoreBuilder {
|
pub(crate) fn types(&mut self, types: Types) -> &mut IgnoreBuilder {
|
||||||
self.types = Arc::new(types);
|
self.types = Arc::new(types);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new global ignore matcher from the ignore file path given.
|
/// Adds a new global ignore matcher from the ignore file path given.
|
||||||
pub fn add_ignore(&mut self, ig: Gitignore) -> &mut IgnoreBuilder {
|
pub(crate) fn add_ignore(&mut self, ig: Gitignore) -> &mut IgnoreBuilder {
|
||||||
self.explicit_ignores.push(ig);
|
self.explicit_ignores.push(ig);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -640,7 +680,7 @@ impl IgnoreBuilder {
|
|||||||
///
|
///
|
||||||
/// When specifying multiple names, earlier names have lower precedence than
|
/// When specifying multiple names, earlier names have lower precedence than
|
||||||
/// later names.
|
/// later names.
|
||||||
pub fn add_custom_ignore_filename<S: AsRef<OsStr>>(
|
pub(crate) fn add_custom_ignore_filename<S: AsRef<OsStr>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
file_name: S,
|
file_name: S,
|
||||||
) -> &mut IgnoreBuilder {
|
) -> &mut IgnoreBuilder {
|
||||||
@ -651,7 +691,7 @@ impl IgnoreBuilder {
|
|||||||
/// Enables ignoring hidden files.
|
/// Enables ignoring hidden files.
|
||||||
///
|
///
|
||||||
/// This is enabled by default.
|
/// This is enabled by default.
|
||||||
pub fn hidden(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
pub(crate) fn hidden(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||||
self.opts.hidden = yes;
|
self.opts.hidden = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -662,7 +702,7 @@ impl IgnoreBuilder {
|
|||||||
/// supported by search tools such as ripgrep and The Silver Searcher.
|
/// supported by search tools such as ripgrep and The Silver Searcher.
|
||||||
///
|
///
|
||||||
/// This is enabled by default.
|
/// This is enabled by default.
|
||||||
pub fn ignore(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
pub(crate) fn ignore(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||||
self.opts.ignore = yes;
|
self.opts.ignore = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -673,7 +713,7 @@ impl IgnoreBuilder {
|
|||||||
/// file path given are respected. Otherwise, they are ignored.
|
/// file path given are respected. Otherwise, they are ignored.
|
||||||
///
|
///
|
||||||
/// This is enabled by default.
|
/// This is enabled by default.
|
||||||
pub fn parents(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
pub(crate) fn parents(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||||
self.opts.parents = yes;
|
self.opts.parents = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -686,7 +726,7 @@ impl IgnoreBuilder {
|
|||||||
/// This overwrites any previous global gitignore setting.
|
/// This overwrites any previous global gitignore setting.
|
||||||
///
|
///
|
||||||
/// This is enabled by default.
|
/// This is enabled by default.
|
||||||
pub fn git_global(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
pub(crate) fn git_global(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||||
self.opts.git_global = yes;
|
self.opts.git_global = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -697,7 +737,7 @@ impl IgnoreBuilder {
|
|||||||
/// man page.
|
/// man page.
|
||||||
///
|
///
|
||||||
/// This is enabled by default.
|
/// This is enabled by default.
|
||||||
pub fn git_ignore(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
pub(crate) fn git_ignore(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||||
self.opts.git_ignore = yes;
|
self.opts.git_ignore = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -708,7 +748,7 @@ impl IgnoreBuilder {
|
|||||||
/// `gitignore` man page.
|
/// `gitignore` man page.
|
||||||
///
|
///
|
||||||
/// This is enabled by default.
|
/// This is enabled by default.
|
||||||
pub fn git_exclude(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
pub(crate) fn git_exclude(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||||
self.opts.git_exclude = yes;
|
self.opts.git_exclude = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -718,7 +758,7 @@ impl IgnoreBuilder {
|
|||||||
///
|
///
|
||||||
/// When disabled, git-related ignore rules are applied even when searching
|
/// When disabled, git-related ignore rules are applied even when searching
|
||||||
/// outside a git repository.
|
/// outside a git repository.
|
||||||
pub fn require_git(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
pub(crate) fn require_git(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||||
self.opts.require_git = yes;
|
self.opts.require_git = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -726,7 +766,7 @@ impl IgnoreBuilder {
|
|||||||
/// Process ignore files case insensitively
|
/// Process ignore files case insensitively
|
||||||
///
|
///
|
||||||
/// This is disabled by default.
|
/// This is disabled by default.
|
||||||
pub fn ignore_case_insensitive(
|
pub(crate) fn ignore_case_insensitive(
|
||||||
&mut self,
|
&mut self,
|
||||||
yes: bool,
|
yes: bool,
|
||||||
) -> &mut IgnoreBuilder {
|
) -> &mut IgnoreBuilder {
|
||||||
@ -743,7 +783,7 @@ impl IgnoreBuilder {
|
|||||||
/// precedence than later names).
|
/// precedence than later names).
|
||||||
///
|
///
|
||||||
/// I/O errors are ignored.
|
/// I/O errors are ignored.
|
||||||
pub fn create_gitignore<T: AsRef<OsStr>>(
|
pub(crate) fn create_gitignore<T: AsRef<OsStr>>(
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
dir_for_ignorefile: &Path,
|
dir_for_ignorefile: &Path,
|
||||||
names: &[T],
|
names: &[T],
|
||||||
@ -836,22 +876,19 @@ fn resolve_git_commondir(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fs::{self, File};
|
use std::{io::Write, path::Path};
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::dir::IgnoreBuilder;
|
use crate::{
|
||||||
use crate::gitignore::Gitignore;
|
dir::IgnoreBuilder, gitignore::Gitignore, tests::TempDir, Error,
|
||||||
use crate::tests::TempDir;
|
};
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
fn wfile<P: AsRef<Path>>(path: P, contents: &str) {
|
fn wfile<P: AsRef<Path>>(path: P, contents: &str) {
|
||||||
let mut file = File::create(path).unwrap();
|
let mut file = std::fs::File::create(path).unwrap();
|
||||||
file.write_all(contents.as_bytes()).unwrap();
|
file.write_all(contents.as_bytes()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mkdirp<P: AsRef<Path>>(path: P) {
|
fn mkdirp<P: AsRef<Path>>(path: P) {
|
||||||
fs::create_dir_all(path).unwrap();
|
std::fs::create_dir_all(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn partial(err: Error) -> Vec<Error> {
|
fn partial(err: Error) -> Vec<Error> {
|
||||||
@ -1168,7 +1205,7 @@ mod tests {
|
|||||||
assert!(ignore.matched("ignore_me", false).is_ignore());
|
assert!(ignore.matched("ignore_me", false).is_ignore());
|
||||||
|
|
||||||
// missing commondir file
|
// missing commondir file
|
||||||
assert!(fs::remove_file(commondir_path()).is_ok());
|
assert!(std::fs::remove_file(commondir_path()).is_ok());
|
||||||
let (_, err) = ib.add_child(td.path().join("linked-worktree"));
|
let (_, err) = ib.add_child(td.path().join("linked-worktree"));
|
||||||
// We squash the error in this case, because it occurs in repositories
|
// We squash the error in this case, because it occurs in repositories
|
||||||
// that are not linked worktrees but have submodules.
|
// that are not linked worktrees but have submodules.
|
||||||
|
@ -7,20 +7,22 @@ Note that this module implements the specification as described in the
|
|||||||
the `git` command line tool.
|
the `git` command line tool.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::{
|
||||||
use std::env;
|
fs::File,
|
||||||
use std::fs::File;
|
io::{BufRead, BufReader, Read},
|
||||||
use std::io::{self, BufRead, Read};
|
path::{Path, PathBuf},
|
||||||
use std::path::{Path, PathBuf};
|
sync::Arc,
|
||||||
use std::str;
|
};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use globset::{Candidate, GlobBuilder, GlobSet, GlobSetBuilder};
|
use {
|
||||||
use regex::bytes::Regex;
|
globset::{Candidate, GlobBuilder, GlobSet, GlobSetBuilder},
|
||||||
use thread_local::ThreadLocal;
|
regex_automata::util::pool::Pool,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::pathutil::{is_file_name, strip_prefix};
|
use crate::{
|
||||||
use crate::{Error, Match, PartialErrorBuilder};
|
pathutil::{is_file_name, strip_prefix},
|
||||||
|
Error, Match, PartialErrorBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
/// Glob represents a single glob in a gitignore file.
|
/// Glob represents a single glob in a gitignore file.
|
||||||
///
|
///
|
||||||
@ -82,7 +84,7 @@ pub struct Gitignore {
|
|||||||
globs: Vec<Glob>,
|
globs: Vec<Glob>,
|
||||||
num_ignores: u64,
|
num_ignores: u64,
|
||||||
num_whitelists: u64,
|
num_whitelists: u64,
|
||||||
matches: Option<Arc<ThreadLocal<RefCell<Vec<usize>>>>>,
|
matches: Option<Arc<Pool<Vec<usize>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gitignore {
|
impl Gitignore {
|
||||||
@ -249,8 +251,7 @@ impl Gitignore {
|
|||||||
return Match::None;
|
return Match::None;
|
||||||
}
|
}
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let _matches = self.matches.as_ref().unwrap().get_or_default();
|
let mut matches = self.matches.as_ref().unwrap().get();
|
||||||
let mut matches = _matches.borrow_mut();
|
|
||||||
let candidate = Candidate::new(path);
|
let candidate = Candidate::new(path);
|
||||||
self.set.matches_candidate_into(&candidate, &mut *matches);
|
self.set.matches_candidate_into(&candidate, &mut *matches);
|
||||||
for &i in matches.iter().rev() {
|
for &i in matches.iter().rev() {
|
||||||
@ -337,12 +338,12 @@ impl GitignoreBuilder {
|
|||||||
.build()
|
.build()
|
||||||
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
|
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
|
||||||
Ok(Gitignore {
|
Ok(Gitignore {
|
||||||
set: set,
|
set,
|
||||||
root: self.root.clone(),
|
root: self.root.clone(),
|
||||||
globs: self.globs.clone(),
|
globs: self.globs.clone(),
|
||||||
num_ignores: nignore as u64,
|
num_ignores: nignore as u64,
|
||||||
num_whitelists: nwhite as u64,
|
num_whitelists: nwhite as u64,
|
||||||
matches: Some(Arc::new(ThreadLocal::default())),
|
matches: Some(Arc::new(Pool::new(|| vec![]))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +390,8 @@ impl GitignoreBuilder {
|
|||||||
Err(err) => return Some(Error::Io(err).with_path(path)),
|
Err(err) => return Some(Error::Io(err).with_path(path)),
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
};
|
};
|
||||||
let rdr = io::BufReader::new(file);
|
log::debug!("opened gitignore file: {}", path.display());
|
||||||
|
let rdr = BufReader::new(file);
|
||||||
let mut errs = PartialErrorBuilder::default();
|
let mut errs = PartialErrorBuilder::default();
|
||||||
for (i, line) in rdr.lines().enumerate() {
|
for (i, line) in rdr.lines().enumerate() {
|
||||||
let lineno = (i + 1) as u64;
|
let lineno = (i + 1) as u64;
|
||||||
@ -448,7 +450,7 @@ impl GitignoreBuilder {
|
|||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
let mut glob = Glob {
|
let mut glob = Glob {
|
||||||
from: from,
|
from,
|
||||||
original: line.to_string(),
|
original: line.to_string(),
|
||||||
actual: String::new(),
|
actual: String::new(),
|
||||||
is_whitelist: false,
|
is_whitelist: false,
|
||||||
@ -474,10 +476,13 @@ impl GitignoreBuilder {
|
|||||||
}
|
}
|
||||||
// If it ends with a slash, then this should only match directories,
|
// If it ends with a slash, then this should only match directories,
|
||||||
// but the slash should otherwise not be used while globbing.
|
// but the slash should otherwise not be used while globbing.
|
||||||
if let Some((i, c)) = line.char_indices().rev().nth(0) {
|
if line.as_bytes().last() == Some(&b'/') {
|
||||||
if c == '/' {
|
glob.is_only_dir = true;
|
||||||
glob.is_only_dir = true;
|
line = &line[..line.len() - 1];
|
||||||
line = &line[..i];
|
// If the slash was escaped, then remove the escape.
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/2236
|
||||||
|
if line.as_bytes().last() == Some(&b'\\') {
|
||||||
|
line = &line[..line.len() - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glob.actual = line.to_string();
|
glob.actual = line.to_string();
|
||||||
@ -530,7 +535,7 @@ impl GitignoreBuilder {
|
|||||||
/// Return the file path of the current environment's global gitignore file.
|
/// Return the file path of the current environment's global gitignore file.
|
||||||
///
|
///
|
||||||
/// Note that the file path returned may not exist.
|
/// Note that the file path returned may not exist.
|
||||||
fn gitconfig_excludes_path() -> Option<PathBuf> {
|
pub fn gitconfig_excludes_path() -> Option<PathBuf> {
|
||||||
// git supports $HOME/.gitconfig and $XDG_CONFIG_HOME/git/config. Notably,
|
// git supports $HOME/.gitconfig and $XDG_CONFIG_HOME/git/config. Notably,
|
||||||
// both can be active at the same time, where $HOME/.gitconfig takes
|
// both can be active at the same time, where $HOME/.gitconfig takes
|
||||||
// precedent. So if $HOME/.gitconfig defines a `core.excludesFile`, then
|
// precedent. So if $HOME/.gitconfig defines a `core.excludesFile`, then
|
||||||
@ -555,7 +560,7 @@ fn gitconfig_home_contents() -> Option<Vec<u8>> {
|
|||||||
};
|
};
|
||||||
let mut file = match File::open(home.join(".gitconfig")) {
|
let mut file = match File::open(home.join(".gitconfig")) {
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
Ok(file) => io::BufReader::new(file),
|
Ok(file) => BufReader::new(file),
|
||||||
};
|
};
|
||||||
let mut contents = vec![];
|
let mut contents = vec![];
|
||||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||||
@ -564,13 +569,13 @@ fn gitconfig_home_contents() -> Option<Vec<u8>> {
|
|||||||
/// Returns the file contents of git's global config file, if one exists, in
|
/// Returns the file contents of git's global config file, if one exists, in
|
||||||
/// the user's XDG_CONFIG_HOME directory.
|
/// the user's XDG_CONFIG_HOME directory.
|
||||||
fn gitconfig_xdg_contents() -> Option<Vec<u8>> {
|
fn gitconfig_xdg_contents() -> Option<Vec<u8>> {
|
||||||
let path = env::var_os("XDG_CONFIG_HOME")
|
let path = std::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(|| home_dir().map(|p| p.join(".config")))
|
.or_else(|| home_dir().map(|p| p.join(".config")))
|
||||||
.map(|x| x.join("git/config"));
|
.map(|x| x.join("git/config"));
|
||||||
let mut file = match path.and_then(|p| File::open(p).ok()) {
|
let mut file = match path.and_then(|p| File::open(p).ok()) {
|
||||||
None => return None,
|
None => return None,
|
||||||
Some(file) => io::BufReader::new(file),
|
Some(file) => BufReader::new(file),
|
||||||
};
|
};
|
||||||
let mut contents = vec![];
|
let mut contents = vec![];
|
||||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||||
@ -580,7 +585,7 @@ fn gitconfig_xdg_contents() -> Option<Vec<u8>> {
|
|||||||
///
|
///
|
||||||
/// Specifically, this respects XDG_CONFIG_HOME.
|
/// Specifically, this respects XDG_CONFIG_HOME.
|
||||||
fn excludes_file_default() -> Option<PathBuf> {
|
fn excludes_file_default() -> Option<PathBuf> {
|
||||||
env::var_os("XDG_CONFIG_HOME")
|
std::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(|| home_dir().map(|p| p.join(".config")))
|
.or_else(|| home_dir().map(|p| p.join(".config")))
|
||||||
.map(|x| x.join("git/ignore"))
|
.map(|x| x.join("git/ignore"))
|
||||||
@ -589,18 +594,28 @@ fn excludes_file_default() -> Option<PathBuf> {
|
|||||||
/// Extract git's `core.excludesfile` config setting from the raw file contents
|
/// Extract git's `core.excludesfile` config setting from the raw file contents
|
||||||
/// given.
|
/// given.
|
||||||
fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
|
fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use regex_automata::{meta::Regex, util::syntax};
|
||||||
|
|
||||||
// N.B. This is the lazy approach, and isn't technically correct, but
|
// N.B. This is the lazy approach, and isn't technically correct, but
|
||||||
// probably works in more circumstances. I guess we would ideally have
|
// probably works in more circumstances. I guess we would ideally have
|
||||||
// a full INI parser. Yuck.
|
// a full INI parser. Yuck.
|
||||||
lazy_static::lazy_static! {
|
static RE: OnceLock<Regex> = OnceLock::new();
|
||||||
static ref RE: Regex =
|
let re = RE.get_or_init(|| {
|
||||||
Regex::new(r"(?im)^\s*excludesfile\s*=\s*(.+)\s*$").unwrap();
|
Regex::builder()
|
||||||
};
|
.configure(Regex::config().utf8_empty(false))
|
||||||
let caps = match RE.captures(data) {
|
.syntax(syntax::Config::new().utf8(false))
|
||||||
None => return None,
|
.build(r#"(?im-u)^\s*excludesfile\s*=\s*"?\s*(\S+?)\s*"?\s*$"#)
|
||||||
Some(caps) => caps,
|
.unwrap()
|
||||||
};
|
});
|
||||||
str::from_utf8(&caps[1]).ok().map(|s| PathBuf::from(expand_tilde(s)))
|
// We don't care about amortizing allocs here I think. This should only
|
||||||
|
// be called ~once per traversal or so? (Although it's not guaranteed...)
|
||||||
|
let mut caps = re.create_captures();
|
||||||
|
re.captures(data, &mut caps);
|
||||||
|
let span = caps.get_group(1)?;
|
||||||
|
let candidate = &data[span];
|
||||||
|
std::str::from_utf8(candidate).ok().map(|s| PathBuf::from(expand_tilde(s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands ~ in file paths to the value of $HOME.
|
/// Expands ~ in file paths to the value of $HOME.
|
||||||
@ -614,18 +629,18 @@ fn expand_tilde(path: &str) -> String {
|
|||||||
|
|
||||||
/// Returns the location of the user's home directory.
|
/// Returns the location of the user's home directory.
|
||||||
fn home_dir() -> Option<PathBuf> {
|
fn home_dir() -> Option<PathBuf> {
|
||||||
// We're fine with using env::home_dir for now. Its bugs are, IMO, pretty
|
// We're fine with using std::env::home_dir for now. Its bugs are, IMO,
|
||||||
// minor corner cases. We should still probably eventually migrate to
|
// pretty minor corner cases.
|
||||||
// the `dirs` crate to get a proper implementation.
|
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
env::home_dir()
|
std::env::home_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Gitignore, GitignoreBuilder};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use super::{Gitignore, GitignoreBuilder};
|
||||||
|
|
||||||
fn gi_from_str<P: AsRef<Path>>(root: P, s: &str) -> Gitignore {
|
fn gi_from_str<P: AsRef<Path>>(root: P, s: &str) -> Gitignore {
|
||||||
let mut builder = GitignoreBuilder::new(root);
|
let mut builder = GitignoreBuilder::new(root);
|
||||||
builder.add_str(None, s).unwrap();
|
builder.add_str(None, s).unwrap();
|
||||||
@ -758,6 +773,22 @@ mod tests {
|
|||||||
assert!(super::parse_excludes_file(&data).is_none());
|
assert!(super::parse_excludes_file(&data).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_excludes_file4() {
|
||||||
|
let data = bytes("[core]\nexcludesFile = \"~/foo/bar\"");
|
||||||
|
let got = super::parse_excludes_file(&data);
|
||||||
|
assert_eq!(
|
||||||
|
path_string(got.unwrap()),
|
||||||
|
super::expand_tilde("~/foo/bar")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_excludes_file5() {
|
||||||
|
let data = bytes("[core]\nexcludesFile = \" \"~/foo/bar \" \"");
|
||||||
|
assert!(super::parse_excludes_file(&data).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/106
|
// See: https://github.com/BurntSushi/ripgrep/issues/106
|
||||||
#[test]
|
#[test]
|
||||||
fn regression_106() {
|
fn regression_106() {
|
||||||
|
@ -46,9 +46,6 @@ See the documentation for `WalkBuilder` for many other options.
|
|||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub use crate::walk::{
|
pub use crate::walk::{
|
||||||
@ -101,7 +98,7 @@ pub enum Error {
|
|||||||
child: PathBuf,
|
child: PathBuf,
|
||||||
},
|
},
|
||||||
/// An error that occurs when doing I/O, such as reading an ignore file.
|
/// An error that occurs when doing I/O, such as reading an ignore file.
|
||||||
Io(io::Error),
|
Io(std::io::Error),
|
||||||
/// An error that occurs when trying to parse a glob.
|
/// An error that occurs when trying to parse a glob.
|
||||||
Glob {
|
Glob {
|
||||||
/// The original glob that caused this error. This glob, when
|
/// The original glob that caused this error. This glob, when
|
||||||
@ -125,21 +122,23 @@ impl Clone for Error {
|
|||||||
match *self {
|
match *self {
|
||||||
Error::Partial(ref errs) => Error::Partial(errs.clone()),
|
Error::Partial(ref errs) => Error::Partial(errs.clone()),
|
||||||
Error::WithLineNumber { line, ref err } => {
|
Error::WithLineNumber { line, ref err } => {
|
||||||
Error::WithLineNumber { line: line, err: err.clone() }
|
Error::WithLineNumber { line, err: err.clone() }
|
||||||
}
|
}
|
||||||
Error::WithPath { ref path, ref err } => {
|
Error::WithPath { ref path, ref err } => {
|
||||||
Error::WithPath { path: path.clone(), err: err.clone() }
|
Error::WithPath { path: path.clone(), err: err.clone() }
|
||||||
}
|
}
|
||||||
Error::WithDepth { depth, ref err } => {
|
Error::WithDepth { depth, ref err } => {
|
||||||
Error::WithDepth { depth: depth, err: err.clone() }
|
Error::WithDepth { depth, err: err.clone() }
|
||||||
}
|
}
|
||||||
Error::Loop { ref ancestor, ref child } => Error::Loop {
|
Error::Loop { ref ancestor, ref child } => Error::Loop {
|
||||||
ancestor: ancestor.clone(),
|
ancestor: ancestor.clone(),
|
||||||
child: child.clone(),
|
child: child.clone(),
|
||||||
},
|
},
|
||||||
Error::Io(ref err) => match err.raw_os_error() {
|
Error::Io(ref err) => match err.raw_os_error() {
|
||||||
Some(e) => Error::Io(io::Error::from_raw_os_error(e)),
|
Some(e) => Error::Io(std::io::Error::from_raw_os_error(e)),
|
||||||
None => Error::Io(io::Error::new(err.kind(), err.to_string())),
|
None => {
|
||||||
|
Error::Io(std::io::Error::new(err.kind(), err.to_string()))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Error::Glob { ref glob, ref err } => {
|
Error::Glob { ref glob, ref err } => {
|
||||||
Error::Glob { glob: glob.clone(), err: err.clone() }
|
Error::Glob { glob: glob.clone(), err: err.clone() }
|
||||||
@ -183,22 +182,22 @@ impl Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspect the original [`io::Error`] if there is one.
|
/// Inspect the original [`std::io::Error`] if there is one.
|
||||||
///
|
///
|
||||||
/// [`None`] is returned if the [`Error`] doesn't correspond to an
|
/// [`None`] is returned if the [`Error`] doesn't correspond to an
|
||||||
/// [`io::Error`]. This might happen, for example, when the error was
|
/// [`std::io::Error`]. This might happen, for example, when the error was
|
||||||
/// produced because a cycle was found in the directory tree while
|
/// produced because a cycle was found in the directory tree while
|
||||||
/// following symbolic links.
|
/// following symbolic links.
|
||||||
///
|
///
|
||||||
/// This method returns a borrowed value that is bound to the lifetime of the [`Error`]. To
|
/// This method returns a borrowed value that is bound to the lifetime of the [`Error`]. To
|
||||||
/// obtain an owned value, the [`into_io_error`] can be used instead.
|
/// obtain an owned value, the [`into_io_error`] can be used instead.
|
||||||
///
|
///
|
||||||
/// > This is the original [`io::Error`] and is _not_ the same as
|
/// > This is the original [`std::io::Error`] and is _not_ the same as
|
||||||
/// > [`impl From<Error> for std::io::Error`][impl] which contains additional context about the
|
/// > [`impl From<Error> for std::io::Error`][impl] which contains
|
||||||
/// error.
|
/// > additional context about the error.
|
||||||
///
|
///
|
||||||
/// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None
|
/// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None
|
||||||
/// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
|
/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
|
||||||
/// [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html
|
/// [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html
|
||||||
/// [`Error`]: struct.Error.html
|
/// [`Error`]: struct.Error.html
|
||||||
/// [`into_io_error`]: struct.Error.html#method.into_io_error
|
/// [`into_io_error`]: struct.Error.html#method.into_io_error
|
||||||
@ -224,10 +223,10 @@ impl Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Similar to [`io_error`] except consumes self to convert to the original
|
/// Similar to [`io_error`] except consumes self to convert to the original
|
||||||
/// [`io::Error`] if one exists.
|
/// [`std::io::Error`] if one exists.
|
||||||
///
|
///
|
||||||
/// [`io_error`]: struct.Error.html#method.io_error
|
/// [`io_error`]: struct.Error.html#method.io_error
|
||||||
/// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
|
/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
|
||||||
pub fn into_io_error(self) -> Option<std::io::Error> {
|
pub fn into_io_error(self) -> Option<std::io::Error> {
|
||||||
match self {
|
match self {
|
||||||
Error::Partial(mut errs) => {
|
Error::Partial(mut errs) => {
|
||||||
@ -268,7 +267,7 @@ impl Error {
|
|||||||
|
|
||||||
/// Turn an error into a tagged error with the given depth.
|
/// Turn an error into a tagged error with the given depth.
|
||||||
fn with_depth(self, depth: usize) -> Error {
|
fn with_depth(self, depth: usize) -> Error {
|
||||||
Error::WithDepth { depth: depth, err: Box::new(self) }
|
Error::WithDepth { depth, err: Box::new(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turn an error into a tagged error with the given file path and line
|
/// Turn an error into a tagged error with the given file path and line
|
||||||
@ -287,7 +286,7 @@ impl Error {
|
|||||||
let depth = err.depth();
|
let depth = err.depth();
|
||||||
if let (Some(anc), Some(child)) = (err.loop_ancestor(), err.path()) {
|
if let (Some(anc), Some(child)) = (err.loop_ancestor(), err.path()) {
|
||||||
return Error::WithDepth {
|
return Error::WithDepth {
|
||||||
depth: depth,
|
depth,
|
||||||
err: Box::new(Error::Loop {
|
err: Box::new(Error::Loop {
|
||||||
ancestor: anc.to_path_buf(),
|
ancestor: anc.to_path_buf(),
|
||||||
child: child.to_path_buf(),
|
child: child.to_path_buf(),
|
||||||
@ -295,15 +294,15 @@ impl Error {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
let path = err.path().map(|p| p.to_path_buf());
|
let path = err.path().map(|p| p.to_path_buf());
|
||||||
let mut ig_err = Error::Io(io::Error::from(err));
|
let mut ig_err = Error::Io(std::io::Error::from(err));
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
ig_err = Error::WithPath { path: path, err: Box::new(ig_err) };
|
ig_err = Error::WithPath { path, err: Box::new(ig_err) };
|
||||||
}
|
}
|
||||||
ig_err
|
ig_err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for Error {
|
impl std::error::Error for Error {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
@ -320,8 +319,8 @@ impl error::Error for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Partial(ref errs) => {
|
Error::Partial(ref errs) => {
|
||||||
let msgs: Vec<String> =
|
let msgs: Vec<String> =
|
||||||
@ -359,8 +358,8 @@ impl fmt::Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
impl From<std::io::Error> for Error {
|
||||||
fn from(err: io::Error) -> Error {
|
fn from(err: std::io::Error) -> Error {
|
||||||
Error::Io(err)
|
Error::Io(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,19 +487,18 @@ impl<T> Match<T> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::env;
|
use std::{
|
||||||
use std::error;
|
env, fs,
|
||||||
use std::fs;
|
path::{Path, PathBuf},
|
||||||
use std::path::{Path, PathBuf};
|
};
|
||||||
use std::result;
|
|
||||||
|
|
||||||
/// A convenient result type alias.
|
/// A convenient result type alias.
|
||||||
pub type Result<T> =
|
pub(crate) type Result<T> =
|
||||||
result::Result<T, Box<dyn error::Error + Send + Sync>>;
|
std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
|
||||||
macro_rules! err {
|
macro_rules! err {
|
||||||
($($tt:tt)*) => {
|
($($tt:tt)*) => {
|
||||||
Box::<dyn error::Error + Send + Sync>::from(format!($($tt)*))
|
Box::<dyn std::error::Error + Send + Sync>::from(format!($($tt)*))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,10 @@ line tools.
|
|||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::gitignore::{self, Gitignore, GitignoreBuilder};
|
use crate::{
|
||||||
use crate::{Error, Match};
|
gitignore::{self, Gitignore, GitignoreBuilder},
|
||||||
|
Error, Match,
|
||||||
|
};
|
||||||
|
|
||||||
/// Glob represents a single glob in an override matcher.
|
/// Glob represents a single glob in an override matcher.
|
||||||
///
|
///
|
||||||
@ -21,9 +23,11 @@ use crate::{Error, Match};
|
|||||||
/// The lifetime `'a` refers to the lifetime of the matcher that produced
|
/// The lifetime `'a` refers to the lifetime of the matcher that produced
|
||||||
/// this glob.
|
/// this glob.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct Glob<'a>(GlobInner<'a>);
|
pub struct Glob<'a>(GlobInner<'a>);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(dead_code)]
|
||||||
enum GlobInner<'a> {
|
enum GlobInner<'a> {
|
||||||
/// No glob matched, but the file path should still be ignored.
|
/// No glob matched, but the file path should still be ignored.
|
||||||
UnmatchedIgnore,
|
UnmatchedIgnore,
|
||||||
@ -106,6 +110,7 @@ impl Override {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a matcher for a set of glob overrides.
|
/// Builds a matcher for a set of glob overrides.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct OverrideBuilder {
|
pub struct OverrideBuilder {
|
||||||
builder: GitignoreBuilder,
|
builder: GitignoreBuilder,
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::ffi::OsStr;
|
use std::{ffi::OsStr, path::Path};
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::walk::DirEntry;
|
use crate::walk::DirEntry;
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ use crate::walk::DirEntry;
|
|||||||
///
|
///
|
||||||
/// On Unix, this implements a more optimized check.
|
/// On Unix, this implements a more optimized check.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn is_hidden(dent: &DirEntry) -> bool {
|
pub(crate) fn is_hidden(dent: &DirEntry) -> bool {
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
if let Some(name) = file_name(dent.path()) {
|
if let Some(name) = file_name(dent.path()) {
|
||||||
@ -26,7 +25,7 @@ pub fn is_hidden(dent: &DirEntry) -> bool {
|
|||||||
/// * The base name of the path starts with a `.`.
|
/// * The base name of the path starts with a `.`.
|
||||||
/// * The file attributes have the `HIDDEN` property set.
|
/// * The file attributes have the `HIDDEN` property set.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn is_hidden(dent: &DirEntry) -> bool {
|
pub(crate) fn is_hidden(dent: &DirEntry) -> bool {
|
||||||
use std::os::windows::fs::MetadataExt;
|
use std::os::windows::fs::MetadataExt;
|
||||||
use winapi_util::file;
|
use winapi_util::file;
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ pub fn is_hidden(dent: &DirEntry) -> bool {
|
|||||||
///
|
///
|
||||||
/// This only returns true if the base name of the path starts with a `.`.
|
/// This only returns true if the base name of the path starts with a `.`.
|
||||||
#[cfg(not(any(unix, windows)))]
|
#[cfg(not(any(unix, windows)))]
|
||||||
pub fn is_hidden(dent: &DirEntry) -> bool {
|
pub(crate) fn is_hidden(dent: &DirEntry) -> bool {
|
||||||
if let Some(name) = file_name(dent.path()) {
|
if let Some(name) = file_name(dent.path()) {
|
||||||
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||||
} else {
|
} else {
|
||||||
@ -61,7 +60,7 @@ pub fn is_hidden(dent: &DirEntry) -> bool {
|
|||||||
///
|
///
|
||||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
pub(crate) fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||||
prefix: &'a P,
|
prefix: &'a P,
|
||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
) -> Option<&'a Path> {
|
) -> Option<&'a Path> {
|
||||||
@ -80,7 +79,7 @@ pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
|||||||
///
|
///
|
||||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
pub(crate) fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||||
prefix: &'a P,
|
prefix: &'a P,
|
||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
) -> Option<&'a Path> {
|
) -> Option<&'a Path> {
|
||||||
@ -90,10 +89,11 @@ pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
|||||||
/// Returns true if this file path is just a file name. i.e., Its parent is
|
/// Returns true if this file path is just a file name. i.e., Its parent is
|
||||||
/// the empty string.
|
/// the empty string.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
pub(crate) fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
||||||
use memchr::memchr;
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
use memchr::memchr;
|
||||||
|
|
||||||
let path = path.as_ref().as_os_str().as_bytes();
|
let path = path.as_ref().as_os_str().as_bytes();
|
||||||
memchr(b'/', path).is_none()
|
memchr(b'/', path).is_none()
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
|||||||
/// Returns true if this file path is just a file name. i.e., Its parent is
|
/// Returns true if this file path is just a file name. i.e., Its parent is
|
||||||
/// the empty string.
|
/// the empty string.
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
pub(crate) fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
||||||
path.as_ref().parent().map(|p| p.as_os_str().is_empty()).unwrap_or(false)
|
path.as_ref().parent().map(|p| p.as_os_str().is_empty()).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
|||||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||||
/// file_name will return None.
|
/// file_name will return None.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
||||||
path: &'a P,
|
path: &'a P,
|
||||||
) -> Option<&'a OsStr> {
|
) -> Option<&'a OsStr> {
|
||||||
use memchr::memrchr;
|
use memchr::memrchr;
|
||||||
@ -135,7 +135,7 @@ pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
|||||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||||
/// file_name will return None.
|
/// file_name will return None.
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
||||||
path: &'a P,
|
path: &'a P,
|
||||||
) -> Option<&'a OsStr> {
|
) -> Option<&'a OsStr> {
|
||||||
path.as_ref().file_name()
|
path.as_ref().file_name()
|
||||||
|
@ -84,18 +84,14 @@ assert!(matcher.matched("y.cpp", false).is_whitelist());
|
|||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
|
use {
|
||||||
use regex::Regex;
|
globset::{GlobBuilder, GlobSet, GlobSetBuilder},
|
||||||
use thread_local::ThreadLocal;
|
regex_automata::util::pool::Pool,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::default_types::DEFAULT_TYPES;
|
use crate::{default_types::DEFAULT_TYPES, pathutil::file_name, Error, Match};
|
||||||
use crate::pathutil::file_name;
|
|
||||||
use crate::{Error, Match};
|
|
||||||
|
|
||||||
/// Glob represents a single glob in a set of file type definitions.
|
/// Glob represents a single glob in a set of file type definitions.
|
||||||
///
|
///
|
||||||
@ -122,10 +118,6 @@ enum GlobInner<'a> {
|
|||||||
Matched {
|
Matched {
|
||||||
/// The file type definition which provided the glob.
|
/// The file type definition which provided the glob.
|
||||||
def: &'a FileTypeDef,
|
def: &'a FileTypeDef,
|
||||||
/// The index of the glob that matched inside the file type definition.
|
|
||||||
which: usize,
|
|
||||||
/// Whether the selection was negated or not.
|
|
||||||
negated: bool,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +177,7 @@ pub struct Types {
|
|||||||
/// The set of all glob selections, used for actual matching.
|
/// The set of all glob selections, used for actual matching.
|
||||||
set: GlobSet,
|
set: GlobSet,
|
||||||
/// Temporary storage for globs that match.
|
/// Temporary storage for globs that match.
|
||||||
matches: Arc<ThreadLocal<RefCell<Vec<usize>>>>,
|
matches: Arc<Pool<Vec<usize>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the type of a selection for a particular file type.
|
/// Indicates the type of a selection for a particular file type.
|
||||||
@ -239,7 +231,7 @@ impl Types {
|
|||||||
has_selected: false,
|
has_selected: false,
|
||||||
glob_to_selection: vec![],
|
glob_to_selection: vec![],
|
||||||
set: GlobSetBuilder::new().build().unwrap(),
|
set: GlobSetBuilder::new().build().unwrap(),
|
||||||
matches: Arc::new(ThreadLocal::default()),
|
matches: Arc::new(Pool::new(|| vec![])),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,17 +279,13 @@ impl Types {
|
|||||||
return Match::None;
|
return Match::None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut matches = self.matches.get_or_default().borrow_mut();
|
let mut matches = self.matches.get();
|
||||||
self.set.matches_into(name, &mut *matches);
|
self.set.matches_into(name, &mut *matches);
|
||||||
// The highest precedent match is the last one.
|
// The highest precedent match is the last one.
|
||||||
if let Some(&i) = matches.last() {
|
if let Some(&i) = matches.last() {
|
||||||
let (isel, iglob) = self.glob_to_selection[i];
|
let (isel, _) = self.glob_to_selection[i];
|
||||||
let sel = &self.selections[isel];
|
let sel = &self.selections[isel];
|
||||||
let glob = Glob(GlobInner::Matched {
|
let glob = Glob(GlobInner::Matched { def: sel.inner() });
|
||||||
def: sel.inner(),
|
|
||||||
which: iglob,
|
|
||||||
negated: sel.is_negated(),
|
|
||||||
});
|
|
||||||
return if sel.is_negated() {
|
return if sel.is_negated() {
|
||||||
Match::Ignore(glob)
|
Match::Ignore(glob)
|
||||||
} else {
|
} else {
|
||||||
@ -364,12 +352,12 @@ impl TypesBuilder {
|
|||||||
.build()
|
.build()
|
||||||
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
|
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
|
||||||
Ok(Types {
|
Ok(Types {
|
||||||
defs: defs,
|
defs,
|
||||||
selections: selections,
|
selections,
|
||||||
has_selected: has_selected,
|
has_selected,
|
||||||
glob_to_selection: glob_to_selection,
|
glob_to_selection,
|
||||||
set: set,
|
set,
|
||||||
matches: Arc::new(ThreadLocal::default()),
|
matches: Arc::new(Pool::new(|| vec![])),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,10 +415,7 @@ impl TypesBuilder {
|
|||||||
/// If `name` is `all` or otherwise contains any character that is not a
|
/// If `name` is `all` or otherwise contains any character that is not a
|
||||||
/// Unicode letter or number, then an error is returned.
|
/// Unicode letter or number, then an error is returned.
|
||||||
pub fn add(&mut self, name: &str, glob: &str) -> Result<(), Error> {
|
pub fn add(&mut self, name: &str, glob: &str) -> Result<(), Error> {
|
||||||
lazy_static::lazy_static! {
|
if name == "all" || !name.chars().all(|c| c.is_alphanumeric()) {
|
||||||
static ref RE: Regex = Regex::new(r"^[\pL\pN]+$").unwrap();
|
|
||||||
};
|
|
||||||
if name == "all" || !RE.is_match(name) {
|
|
||||||
return Err(Error::InvalidDefinition);
|
return Err(Error::InvalidDefinition);
|
||||||
}
|
}
|
||||||
let (key, glob) = (name.to_string(), glob.to_string());
|
let (key, glob) = (name.to_string(), glob.to_string());
|
||||||
@ -496,9 +481,11 @@ impl TypesBuilder {
|
|||||||
/// Add a set of default file type definitions.
|
/// Add a set of default file type definitions.
|
||||||
pub fn add_defaults(&mut self) -> &mut TypesBuilder {
|
pub fn add_defaults(&mut self) -> &mut TypesBuilder {
|
||||||
static MSG: &'static str = "adding a default type should never fail";
|
static MSG: &'static str = "adding a default type should never fail";
|
||||||
for &(name, exts) in DEFAULT_TYPES {
|
for &(names, exts) in DEFAULT_TYPES {
|
||||||
for ext in exts {
|
for name in names {
|
||||||
self.add(name, ext).expect(MSG);
|
for ext in exts {
|
||||||
|
self.add(name, ext).expect(MSG);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -545,6 +532,8 @@ mod tests {
|
|||||||
"html:*.htm",
|
"html:*.htm",
|
||||||
"rust:*.rs",
|
"rust:*.rs",
|
||||||
"js:*.js",
|
"js:*.js",
|
||||||
|
"py:*.py",
|
||||||
|
"python:*.py",
|
||||||
"foo:*.{rs,foo}",
|
"foo:*.{rs,foo}",
|
||||||
"combo:include:html,rust",
|
"combo:include:html,rust",
|
||||||
]
|
]
|
||||||
@ -559,6 +548,8 @@ mod tests {
|
|||||||
matched!(match7, types(), vec!["foo"], vec!["rust"], "main.foo");
|
matched!(match7, types(), vec!["foo"], vec!["rust"], "main.foo");
|
||||||
matched!(match8, types(), vec!["combo"], vec![], "index.html");
|
matched!(match8, types(), vec!["combo"], vec![], "index.html");
|
||||||
matched!(match9, types(), vec!["combo"], vec![], "lib.rs");
|
matched!(match9, types(), vec!["combo"], vec![], "lib.rs");
|
||||||
|
matched!(match10, types(), vec!["py"], vec![], "main.py");
|
||||||
|
matched!(match11, types(), vec!["python"], vec![], "main.py");
|
||||||
|
|
||||||
matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
|
matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
|
||||||
matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
|
matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
|
||||||
@ -566,6 +557,8 @@ mod tests {
|
|||||||
matched!(not, matchnot4, types(), vec!["rust"], vec!["foo"], "main.rs");
|
matched!(not, matchnot4, types(), vec!["rust"], vec!["foo"], "main.rs");
|
||||||
matched!(not, matchnot5, types(), vec!["rust"], vec!["foo"], "main.foo");
|
matched!(not, matchnot5, types(), vec!["rust"], vec!["foo"], "main.foo");
|
||||||
matched!(not, matchnot6, types(), vec!["combo"], vec![], "leftpad.js");
|
matched!(not, matchnot6, types(), vec!["combo"], vec![], "leftpad.js");
|
||||||
|
matched!(not, matchnot7, types(), vec!["py"], vec![], "index.html");
|
||||||
|
matched!(not, matchnot8, types(), vec!["python"], vec![], "doc.md");
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_defs() {
|
fn test_invalid_defs() {
|
||||||
@ -577,7 +570,7 @@ mod tests {
|
|||||||
let original_defs = btypes.definitions();
|
let original_defs = btypes.definitions();
|
||||||
let bad_defs = vec![
|
let bad_defs = vec![
|
||||||
// Reference to type that does not exist
|
// Reference to type that does not exist
|
||||||
"combo:include:html,python",
|
"combo:include:html,qwerty",
|
||||||
// Bad format
|
// Bad format
|
||||||
"combo:foobar:html,rust",
|
"combo:foobar:html,rust",
|
||||||
"",
|
"",
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
use std::cmp;
|
use std::{
|
||||||
use std::ffi::OsStr;
|
cmp::Ordering,
|
||||||
use std::fmt;
|
ffi::OsStr,
|
||||||
use std::fs::{self, FileType, Metadata};
|
fs::{self, FileType, Metadata},
|
||||||
use std::io;
|
io,
|
||||||
use std::path::{Path, PathBuf};
|
path::{Path, PathBuf},
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering},
|
||||||
use std::sync::{Arc, Mutex};
|
sync::Arc,
|
||||||
use std::thread;
|
};
|
||||||
use std::time::Duration;
|
|
||||||
use std::vec;
|
|
||||||
|
|
||||||
use same_file::Handle;
|
use {
|
||||||
use walkdir::{self, WalkDir};
|
crossbeam_deque::{Stealer, Worker as Deque},
|
||||||
|
same_file::Handle,
|
||||||
|
walkdir::WalkDir,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::dir::{Ignore, IgnoreBuilder};
|
use crate::{
|
||||||
use crate::gitignore::GitignoreBuilder;
|
dir::{Ignore, IgnoreBuilder},
|
||||||
use crate::overrides::Override;
|
gitignore::GitignoreBuilder,
|
||||||
use crate::types::Types;
|
overrides::Override,
|
||||||
use crate::{Error, PartialErrorBuilder};
|
types::Types,
|
||||||
|
Error, PartialErrorBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
/// A directory entry with a possible error attached.
|
/// A directory entry with a possible error attached.
|
||||||
///
|
///
|
||||||
@ -36,9 +39,7 @@ impl DirEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The full path that this entry represents.
|
/// The full path that this entry represents.
|
||||||
/// Analogous to [`path`], but moves ownership of the path.
|
/// Analogous to [`DirEntry::path`], but moves ownership of the path.
|
||||||
///
|
|
||||||
/// [`path`]: struct.DirEntry.html#method.path
|
|
||||||
pub fn into_path(self) -> PathBuf {
|
pub fn into_path(self) -> PathBuf {
|
||||||
self.dent.into_path()
|
self.dent.into_path()
|
||||||
}
|
}
|
||||||
@ -107,11 +108,11 @@ impl DirEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_walkdir(dent: walkdir::DirEntry, err: Option<Error>) -> DirEntry {
|
fn new_walkdir(dent: walkdir::DirEntry, err: Option<Error>) -> DirEntry {
|
||||||
DirEntry { dent: DirEntryInner::Walkdir(dent), err: err }
|
DirEntry { dent: DirEntryInner::Walkdir(dent), err }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_raw(dent: DirEntryRaw, err: Option<Error>) -> DirEntry {
|
fn new_raw(dent: DirEntryRaw, err: Option<Error>) -> DirEntry {
|
||||||
DirEntry { dent: DirEntryInner::Raw(dent), err: err }
|
DirEntry { dent: DirEntryInner::Raw(dent), err }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,8 +252,8 @@ struct DirEntryRaw {
|
|||||||
metadata: fs::Metadata,
|
metadata: fs::Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for DirEntryRaw {
|
impl std::fmt::Debug for DirEntryRaw {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
// Leaving out FileType because it doesn't have a debug impl
|
// Leaving out FileType because it doesn't have a debug impl
|
||||||
// in Rust 1.9. We could add it if we really wanted to by manually
|
// in Rust 1.9. We could add it if we really wanted to by manually
|
||||||
// querying each possibly file type. Meh. ---AG
|
// querying each possibly file type. Meh. ---AG
|
||||||
@ -324,7 +325,7 @@ impl DirEntryRaw {
|
|||||||
) -> Result<DirEntryRaw, Error> {
|
) -> Result<DirEntryRaw, Error> {
|
||||||
let ty = ent.file_type().map_err(|err| {
|
let ty = ent.file_type().map_err(|err| {
|
||||||
let err = Error::Io(io::Error::from(err)).with_path(ent.path());
|
let err = Error::Io(io::Error::from(err)).with_path(ent.path());
|
||||||
Error::WithDepth { depth: depth, err: Box::new(err) }
|
Error::WithDepth { depth, err: Box::new(err) }
|
||||||
})?;
|
})?;
|
||||||
DirEntryRaw::from_entry_os(depth, ent, ty)
|
DirEntryRaw::from_entry_os(depth, ent, ty)
|
||||||
}
|
}
|
||||||
@ -337,13 +338,13 @@ impl DirEntryRaw {
|
|||||||
) -> Result<DirEntryRaw, Error> {
|
) -> Result<DirEntryRaw, Error> {
|
||||||
let md = ent.metadata().map_err(|err| {
|
let md = ent.metadata().map_err(|err| {
|
||||||
let err = Error::Io(io::Error::from(err)).with_path(ent.path());
|
let err = Error::Io(io::Error::from(err)).with_path(ent.path());
|
||||||
Error::WithDepth { depth: depth, err: Box::new(err) }
|
Error::WithDepth { depth, err: Box::new(err) }
|
||||||
})?;
|
})?;
|
||||||
Ok(DirEntryRaw {
|
Ok(DirEntryRaw {
|
||||||
path: ent.path(),
|
path: ent.path(),
|
||||||
ty: ty,
|
ty,
|
||||||
follow_link: false,
|
follow_link: false,
|
||||||
depth: depth,
|
depth,
|
||||||
metadata: md,
|
metadata: md,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -358,9 +359,9 @@ impl DirEntryRaw {
|
|||||||
|
|
||||||
Ok(DirEntryRaw {
|
Ok(DirEntryRaw {
|
||||||
path: ent.path(),
|
path: ent.path(),
|
||||||
ty: ty,
|
ty,
|
||||||
follow_link: false,
|
follow_link: false,
|
||||||
depth: depth,
|
depth,
|
||||||
ino: ent.ino(),
|
ino: ent.ino(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -391,7 +392,7 @@ impl DirEntryRaw {
|
|||||||
path: pb,
|
path: pb,
|
||||||
ty: md.file_type(),
|
ty: md.file_type(),
|
||||||
follow_link: link,
|
follow_link: link,
|
||||||
depth: depth,
|
depth,
|
||||||
metadata: md,
|
metadata: md,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -410,7 +411,7 @@ impl DirEntryRaw {
|
|||||||
path: pb,
|
path: pb,
|
||||||
ty: md.file_type(),
|
ty: md.file_type(),
|
||||||
follow_link: link,
|
follow_link: link,
|
||||||
depth: depth,
|
depth,
|
||||||
ino: md.ino(),
|
ino: md.ino(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -494,17 +495,15 @@ pub struct WalkBuilder {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Sorter {
|
enum Sorter {
|
||||||
ByName(
|
ByName(Arc<dyn Fn(&OsStr, &OsStr) -> Ordering + Send + Sync + 'static>),
|
||||||
Arc<dyn Fn(&OsStr, &OsStr) -> cmp::Ordering + Send + Sync + 'static>,
|
ByPath(Arc<dyn Fn(&Path, &Path) -> Ordering + Send + Sync + 'static>),
|
||||||
),
|
|
||||||
ByPath(Arc<dyn Fn(&Path, &Path) -> cmp::Ordering + Send + Sync + 'static>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Filter(Arc<dyn Fn(&DirEntry) -> bool + Send + Sync + 'static>);
|
struct Filter(Arc<dyn Fn(&DirEntry) -> bool + Send + Sync + 'static>);
|
||||||
|
|
||||||
impl fmt::Debug for WalkBuilder {
|
impl std::fmt::Debug for WalkBuilder {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("WalkBuilder")
|
f.debug_struct("WalkBuilder")
|
||||||
.field("paths", &self.paths)
|
.field("paths", &self.paths)
|
||||||
.field("ig_builder", &self.ig_builder)
|
.field("ig_builder", &self.ig_builder)
|
||||||
@ -578,7 +577,7 @@ impl WalkBuilder {
|
|||||||
.into_iter();
|
.into_iter();
|
||||||
let ig_root = self.ig_builder.build();
|
let ig_root = self.ig_builder.build();
|
||||||
Walk {
|
Walk {
|
||||||
its: its,
|
its,
|
||||||
it: None,
|
it: None,
|
||||||
ig_root: ig_root.clone(),
|
ig_root: ig_root.clone(),
|
||||||
ig: ig_root.clone(),
|
ig: ig_root.clone(),
|
||||||
@ -592,7 +591,7 @@ impl WalkBuilder {
|
|||||||
///
|
///
|
||||||
/// Note that this *doesn't* return something that implements `Iterator`.
|
/// Note that this *doesn't* return something that implements `Iterator`.
|
||||||
/// Instead, the returned value must be run with a closure. e.g.,
|
/// Instead, the returned value must be run with a closure. e.g.,
|
||||||
/// `builder.build_parallel().run(|| |path| println!("{:?}", path))`.
|
/// `builder.build_parallel().run(|| |path| { println!("{path:?}"); WalkState::Continue })`.
|
||||||
pub fn build_parallel(&self) -> WalkParallel {
|
pub fn build_parallel(&self) -> WalkParallel {
|
||||||
WalkParallel {
|
WalkParallel {
|
||||||
paths: self.paths.clone().into_iter(),
|
paths: self.paths.clone().into_iter(),
|
||||||
@ -828,7 +827,7 @@ impl WalkBuilder {
|
|||||||
/// Note that this is not used in the parallel iterator.
|
/// Note that this is not used in the parallel iterator.
|
||||||
pub fn sort_by_file_path<F>(&mut self, cmp: F) -> &mut WalkBuilder
|
pub fn sort_by_file_path<F>(&mut self, cmp: F) -> &mut WalkBuilder
|
||||||
where
|
where
|
||||||
F: Fn(&Path, &Path) -> cmp::Ordering + Send + Sync + 'static,
|
F: Fn(&Path, &Path) -> Ordering + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
self.sorter = Some(Sorter::ByPath(Arc::new(cmp)));
|
self.sorter = Some(Sorter::ByPath(Arc::new(cmp)));
|
||||||
self
|
self
|
||||||
@ -847,7 +846,7 @@ impl WalkBuilder {
|
|||||||
/// Note that this is not used in the parallel iterator.
|
/// Note that this is not used in the parallel iterator.
|
||||||
pub fn sort_by_file_name<F>(&mut self, cmp: F) -> &mut WalkBuilder
|
pub fn sort_by_file_name<F>(&mut self, cmp: F) -> &mut WalkBuilder
|
||||||
where
|
where
|
||||||
F: Fn(&OsStr, &OsStr) -> cmp::Ordering + Send + Sync + 'static,
|
F: Fn(&OsStr, &OsStr) -> Ordering + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
self.sorter = Some(Sorter::ByName(Arc::new(cmp)));
|
self.sorter = Some(Sorter::ByName(Arc::new(cmp)));
|
||||||
self
|
self
|
||||||
@ -911,7 +910,7 @@ impl WalkBuilder {
|
|||||||
/// ignore files like `.gitignore` are respected. The precise matching rules
|
/// ignore files like `.gitignore` are respected. The precise matching rules
|
||||||
/// and precedence is explained in the documentation for `WalkBuilder`.
|
/// and precedence is explained in the documentation for `WalkBuilder`.
|
||||||
pub struct Walk {
|
pub struct Walk {
|
||||||
its: vec::IntoIter<(PathBuf, Option<WalkEventIter>)>,
|
its: std::vec::IntoIter<(PathBuf, Option<WalkEventIter>)>,
|
||||||
it: Option<WalkEventIter>,
|
it: Option<WalkEventIter>,
|
||||||
ig_root: Ignore,
|
ig_root: Ignore,
|
||||||
ig: Ignore,
|
ig: Ignore,
|
||||||
@ -941,7 +940,7 @@ impl Walk {
|
|||||||
// overheads; an example of this was a bespoke filesystem layer in
|
// overheads; an example of this was a bespoke filesystem layer in
|
||||||
// Windows that hosted files remotely and would download them on-demand
|
// Windows that hosted files remotely and would download them on-demand
|
||||||
// when particular filesystem operations occurred. Users of this system
|
// when particular filesystem operations occurred. Users of this system
|
||||||
// who ensured correct file-type fileters were being used could still
|
// who ensured correct file-type filters were being used could still
|
||||||
// get unnecessary file access resulting in large downloads.
|
// get unnecessary file access resulting in large downloads.
|
||||||
if should_skip_entry(&self.ig, ent) {
|
if should_skip_entry(&self.ig, ent) {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
@ -1040,6 +1039,8 @@ impl Iterator for Walk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::iter::FusedIterator for Walk {}
|
||||||
|
|
||||||
/// WalkEventIter transforms a WalkDir iterator into an iterator that more
|
/// WalkEventIter transforms a WalkDir iterator into an iterator that more
|
||||||
/// accurately describes the directory tree. Namely, it emits events that are
|
/// accurately describes the directory tree. Namely, it emits events that are
|
||||||
/// one of three types: directory, file or "exit." An "exit" event means that
|
/// one of three types: directory, file or "exit." An "exit" event means that
|
||||||
@ -1123,10 +1124,10 @@ impl WalkState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for constructing a visitor when using
|
/// A builder for constructing a visitor when using [`WalkParallel::visit`].
|
||||||
/// [`WalkParallel::visit`](struct.WalkParallel.html#method.visit). The builder
|
/// The builder will be called for each thread started by `WalkParallel`. The
|
||||||
/// will be called for each thread started by `WalkParallel`. The visitor
|
/// visitor returned from each builder is then called for every directory
|
||||||
/// returned from each builder is then called for every directory entry.
|
/// entry.
|
||||||
pub trait ParallelVisitorBuilder<'s> {
|
pub trait ParallelVisitorBuilder<'s> {
|
||||||
/// Create per-thread `ParallelVisitor`s for `WalkParallel`.
|
/// Create per-thread `ParallelVisitor`s for `WalkParallel`.
|
||||||
fn build(&mut self) -> Box<dyn ParallelVisitor + 's>;
|
fn build(&mut self) -> Box<dyn ParallelVisitor + 's>;
|
||||||
@ -1143,9 +1144,8 @@ impl<'a, 's, P: ParallelVisitorBuilder<'s>> ParallelVisitorBuilder<'s>
|
|||||||
/// Receives files and directories for the current thread.
|
/// Receives files and directories for the current thread.
|
||||||
///
|
///
|
||||||
/// Setup for the traversal can be implemented as part of
|
/// Setup for the traversal can be implemented as part of
|
||||||
/// [`ParallelVisitorBuilder::build`](trait.ParallelVisitorBuilder.html#tymethod.build).
|
/// [`ParallelVisitorBuilder::build`]. Teardown when traversal finishes can be
|
||||||
/// Teardown when traversal finishes can be implemented by implementing the
|
/// implemented by implementing the `Drop` trait on your traversal type.
|
||||||
/// `Drop` trait on your traversal type.
|
|
||||||
pub trait ParallelVisitor: Send {
|
pub trait ParallelVisitor: Send {
|
||||||
/// Receives files and directories for the current thread. This is called
|
/// Receives files and directories for the current thread. This is called
|
||||||
/// once for every directory entry visited by traversal.
|
/// once for every directory entry visited by traversal.
|
||||||
@ -1187,7 +1187,7 @@ impl<'s> ParallelVisitor for FnVisitorImp<'s> {
|
|||||||
///
|
///
|
||||||
/// Unlike `Walk`, this uses multiple threads for traversing a directory.
|
/// Unlike `Walk`, this uses multiple threads for traversing a directory.
|
||||||
pub struct WalkParallel {
|
pub struct WalkParallel {
|
||||||
paths: vec::IntoIter<PathBuf>,
|
paths: std::vec::IntoIter<PathBuf>,
|
||||||
ig_root: Ignore,
|
ig_root: Ignore,
|
||||||
max_filesize: Option<u64>,
|
max_filesize: Option<u64>,
|
||||||
max_depth: Option<usize>,
|
max_depth: Option<usize>,
|
||||||
@ -1228,9 +1228,8 @@ impl WalkParallel {
|
|||||||
/// can be merged together into a single data structure.
|
/// can be merged together into a single data structure.
|
||||||
pub fn visit(mut self, builder: &mut dyn ParallelVisitorBuilder<'_>) {
|
pub fn visit(mut self, builder: &mut dyn ParallelVisitorBuilder<'_>) {
|
||||||
let threads = self.threads();
|
let threads = self.threads();
|
||||||
let stack = Arc::new(Mutex::new(vec![]));
|
let mut stack = vec![];
|
||||||
{
|
{
|
||||||
let mut stack = stack.lock().unwrap();
|
|
||||||
let mut visitor = builder.build();
|
let mut visitor = builder.build();
|
||||||
let mut paths = Vec::new().into_iter();
|
let mut paths = Vec::new().into_iter();
|
||||||
std::mem::swap(&mut paths, &mut self.paths);
|
std::mem::swap(&mut paths, &mut self.paths);
|
||||||
@ -1268,9 +1267,9 @@ impl WalkParallel {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
stack.push(Message::Work(Work {
|
stack.push(Message::Work(Work {
|
||||||
dent: dent,
|
dent,
|
||||||
ignore: self.ig_root.clone(),
|
ignore: self.ig_root.clone(),
|
||||||
root_device: root_device,
|
root_device,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// ... but there's no need to start workers if we don't need them.
|
// ... but there's no need to start workers if we don't need them.
|
||||||
@ -1280,29 +1279,28 @@ impl WalkParallel {
|
|||||||
}
|
}
|
||||||
// Create the workers and then wait for them to finish.
|
// Create the workers and then wait for them to finish.
|
||||||
let quit_now = Arc::new(AtomicBool::new(false));
|
let quit_now = Arc::new(AtomicBool::new(false));
|
||||||
let num_pending =
|
let active_workers = Arc::new(AtomicUsize::new(threads));
|
||||||
Arc::new(AtomicUsize::new(stack.lock().unwrap().len()));
|
let stacks = Stack::new_for_each_thread(threads, stack);
|
||||||
crossbeam_utils::thread::scope(|s| {
|
std::thread::scope(|s| {
|
||||||
let mut handles = vec![];
|
let handles: Vec<_> = stacks
|
||||||
for _ in 0..threads {
|
.into_iter()
|
||||||
let worker = Worker {
|
.map(|stack| Worker {
|
||||||
visitor: builder.build(),
|
visitor: builder.build(),
|
||||||
stack: stack.clone(),
|
stack,
|
||||||
quit_now: quit_now.clone(),
|
quit_now: quit_now.clone(),
|
||||||
num_pending: num_pending.clone(),
|
active_workers: active_workers.clone(),
|
||||||
max_depth: self.max_depth,
|
max_depth: self.max_depth,
|
||||||
max_filesize: self.max_filesize,
|
max_filesize: self.max_filesize,
|
||||||
follow_links: self.follow_links,
|
follow_links: self.follow_links,
|
||||||
skip: self.skip.clone(),
|
skip: self.skip.clone(),
|
||||||
filter: self.filter.clone(),
|
filter: self.filter.clone(),
|
||||||
};
|
})
|
||||||
handles.push(s.spawn(|_| worker.run()));
|
.map(|worker| s.spawn(|| worker.run()))
|
||||||
}
|
.collect();
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
handle.join().unwrap();
|
handle.join().unwrap();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.unwrap(); // Pass along panics from threads
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn threads(&self) -> usize {
|
fn threads(&self) -> usize {
|
||||||
@ -1388,6 +1386,73 @@ impl Work {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A work-stealing stack.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Stack {
|
||||||
|
/// This thread's index.
|
||||||
|
index: usize,
|
||||||
|
/// The thread-local stack.
|
||||||
|
deque: Deque<Message>,
|
||||||
|
/// The work stealers.
|
||||||
|
stealers: Arc<[Stealer<Message>]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stack {
|
||||||
|
/// Create a work-stealing stack for each thread. The given messages
|
||||||
|
/// correspond to the initial paths to start the search at. They will
|
||||||
|
/// be distributed automatically to each stack in a round-robin fashion.
|
||||||
|
fn new_for_each_thread(threads: usize, init: Vec<Message>) -> Vec<Stack> {
|
||||||
|
// Using new_lifo() ensures each worker operates depth-first, not
|
||||||
|
// breadth-first. We do depth-first because a breadth first traversal
|
||||||
|
// on wide directories with a lot of gitignores is disastrous (for
|
||||||
|
// example, searching a directory tree containing all of crates.io).
|
||||||
|
let deques: Vec<Deque<Message>> =
|
||||||
|
std::iter::repeat_with(Deque::new_lifo).take(threads).collect();
|
||||||
|
let stealers = Arc::<[Stealer<Message>]>::from(
|
||||||
|
deques.iter().map(Deque::stealer).collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
let stacks: Vec<Stack> = deques
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, deque)| Stack {
|
||||||
|
index,
|
||||||
|
deque,
|
||||||
|
stealers: stealers.clone(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// Distribute the initial messages.
|
||||||
|
init.into_iter()
|
||||||
|
.zip(stacks.iter().cycle())
|
||||||
|
.for_each(|(m, s)| s.push(m));
|
||||||
|
stacks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a message.
|
||||||
|
fn push(&self, msg: Message) {
|
||||||
|
self.deque.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop a message.
|
||||||
|
fn pop(&self) -> Option<Message> {
|
||||||
|
self.deque.pop().or_else(|| self.steal())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Steal a message from another queue.
|
||||||
|
fn steal(&self) -> Option<Message> {
|
||||||
|
// For fairness, try to steal from index + 1, index + 2, ... len - 1,
|
||||||
|
// then wrap around to 0, 1, ... index - 1.
|
||||||
|
let (left, right) = self.stealers.split_at(self.index);
|
||||||
|
// Don't steal from ourselves
|
||||||
|
let right = &right[1..];
|
||||||
|
|
||||||
|
right
|
||||||
|
.iter()
|
||||||
|
.chain(left.iter())
|
||||||
|
.map(|s| s.steal_batch_and_pop(&self.deque))
|
||||||
|
.find_map(|s| s.success())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A worker is responsible for descending into directories, updating the
|
/// A worker is responsible for descending into directories, updating the
|
||||||
/// ignore matchers, producing new work and invoking the caller's callback.
|
/// ignore matchers, producing new work and invoking the caller's callback.
|
||||||
///
|
///
|
||||||
@ -1395,19 +1460,19 @@ impl Work {
|
|||||||
struct Worker<'s> {
|
struct Worker<'s> {
|
||||||
/// The caller's callback.
|
/// The caller's callback.
|
||||||
visitor: Box<dyn ParallelVisitor + 's>,
|
visitor: Box<dyn ParallelVisitor + 's>,
|
||||||
/// A stack of work to do.
|
/// A work-stealing stack of work to do.
|
||||||
///
|
///
|
||||||
/// We use a stack instead of a channel because a stack lets us visit
|
/// We use a stack instead of a channel because a stack lets us visit
|
||||||
/// directories in depth first order. This can substantially reduce peak
|
/// directories in depth first order. This can substantially reduce peak
|
||||||
/// memory usage by keeping both the number of files path and gitignore
|
/// memory usage by keeping both the number of file paths and gitignore
|
||||||
/// matchers in memory lower.
|
/// matchers in memory lower.
|
||||||
stack: Arc<Mutex<Vec<Message>>>,
|
stack: Stack,
|
||||||
/// Whether all workers should terminate at the next opportunity. Note
|
/// Whether all workers should terminate at the next opportunity. Note
|
||||||
/// that we need this because we don't want other `Work` to be done after
|
/// that we need this because we don't want other `Work` to be done after
|
||||||
/// we quit. We wouldn't need this if have a priority channel.
|
/// we quit. We wouldn't need this if have a priority channel.
|
||||||
quit_now: Arc<AtomicBool>,
|
quit_now: Arc<AtomicBool>,
|
||||||
/// The number of outstanding work items.
|
/// The number of currently active workers.
|
||||||
num_pending: Arc<AtomicUsize>,
|
active_workers: Arc<AtomicUsize>,
|
||||||
/// 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>,
|
||||||
@ -1435,7 +1500,6 @@ impl<'s> Worker<'s> {
|
|||||||
if let WalkState::Quit = self.run_one(work) {
|
if let WalkState::Quit = self.run_one(work) {
|
||||||
self.quit_now();
|
self.quit_now();
|
||||||
}
|
}
|
||||||
self.work_done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1617,23 +1681,20 @@ impl<'s> Worker<'s> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Once num_pending reaches 0, it is impossible for it to
|
if self.deactivate_worker() == 0 {
|
||||||
// ever increase again. Namely, it only reaches 0 once
|
// If deactivate_worker() returns 0, every worker thread
|
||||||
// all jobs have run such that no jobs have produced more
|
// is currently within the critical section between the
|
||||||
// work. We have this guarantee because num_pending is
|
// acquire in deactivate_worker() and the release in
|
||||||
// always incremented before each job is submitted and only
|
// activate_worker() below. For this to happen, every
|
||||||
// decremented once each job is completely finished.
|
// worker's local deque must be simultaneously empty,
|
||||||
// Therefore, if this reaches zero, then there can be no
|
// meaning there is no more work left at all.
|
||||||
// other job running.
|
|
||||||
if self.num_pending() == 0 {
|
|
||||||
// Every other thread is blocked at the next recv().
|
|
||||||
// Send the initial quit message and quit.
|
|
||||||
self.send_quit();
|
self.send_quit();
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Wait for next `Work` or `Quit` message.
|
// Wait for next `Work` or `Quit` message.
|
||||||
loop {
|
loop {
|
||||||
if let Some(v) = self.recv() {
|
if let Some(v) = self.recv() {
|
||||||
|
self.activate_worker();
|
||||||
value = Some(v);
|
value = Some(v);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1641,7 +1702,8 @@ impl<'s> Worker<'s> {
|
|||||||
// CPU waiting, we let the thread sleep for a bit. In
|
// CPU waiting, we let the thread sleep for a bit. In
|
||||||
// general, this tends to only occur once the search is
|
// general, this tends to only occur once the search is
|
||||||
// approaching termination.
|
// approaching termination.
|
||||||
thread::sleep(Duration::from_millis(1));
|
let dur = std::time::Duration::from_millis(1);
|
||||||
|
std::thread::sleep(dur);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1650,41 +1712,37 @@ impl<'s> Worker<'s> {
|
|||||||
|
|
||||||
/// Indicates that all workers should quit immediately.
|
/// Indicates that all workers should quit immediately.
|
||||||
fn quit_now(&self) {
|
fn quit_now(&self) {
|
||||||
self.quit_now.store(true, Ordering::SeqCst);
|
self.quit_now.store(true, AtomicOrdering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this worker should quit immediately.
|
/// Returns true if this worker should quit immediately.
|
||||||
fn is_quit_now(&self) -> bool {
|
fn is_quit_now(&self) -> bool {
|
||||||
self.quit_now.load(Ordering::SeqCst)
|
self.quit_now.load(AtomicOrdering::SeqCst)
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of pending jobs.
|
|
||||||
fn num_pending(&self) -> usize {
|
|
||||||
self.num_pending.load(Ordering::SeqCst)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send work.
|
/// Send work.
|
||||||
fn send(&self, work: Work) {
|
fn send(&self, work: Work) {
|
||||||
self.num_pending.fetch_add(1, Ordering::SeqCst);
|
self.stack.push(Message::Work(work));
|
||||||
let mut stack = self.stack.lock().unwrap();
|
|
||||||
stack.push(Message::Work(work));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a quit message.
|
/// Send a quit message.
|
||||||
fn send_quit(&self) {
|
fn send_quit(&self) {
|
||||||
let mut stack = self.stack.lock().unwrap();
|
self.stack.push(Message::Quit);
|
||||||
stack.push(Message::Quit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive work.
|
/// Receive work.
|
||||||
fn recv(&self) -> Option<Message> {
|
fn recv(&self) -> Option<Message> {
|
||||||
let mut stack = self.stack.lock().unwrap();
|
self.stack.pop()
|
||||||
stack.pop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signal that work has been received.
|
/// Deactivates a worker and returns the number of currently active workers.
|
||||||
fn work_done(&self) {
|
fn deactivate_worker(&self) -> usize {
|
||||||
self.num_pending.fetch_sub(1, Ordering::SeqCst);
|
self.active_workers.fetch_sub(1, AtomicOrdering::Acquire) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reactivates a worker.
|
||||||
|
fn activate_worker(&self) {
|
||||||
|
self.active_workers.fetch_add(1, AtomicOrdering::Release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-matcher"
|
name = "grep-matcher"
|
||||||
version = "0.1.5" #:version
|
version = "0.1.7" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
A trait for regular expressions, with a focus on line oriented search.
|
A trait for regular expressions, with a focus on line oriented search.
|
||||||
@ -10,15 +10,15 @@ homepage = "https://github.com/BurntSushi/ripgrep/tree/master/crates/matcher"
|
|||||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/matcher"
|
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/matcher"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["regex", "pattern", "trait"]
|
keywords = ["regex", "pattern", "trait"]
|
||||||
license = "Unlicense/MIT"
|
license = "Unlicense OR MIT"
|
||||||
autotests = false
|
autotests = false
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memchr = "2.1"
|
memchr = "2.6.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
regex = "1.1"
|
regex = "1.9.5"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "integration"
|
name = "integration"
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::str;
|
|
||||||
|
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
|
||||||
/// Interpolate capture references in `replacement` and write the interpolation
|
/// Interpolate capture references in `replacement` and write the interpolation
|
||||||
@ -12,6 +10,7 @@ use memchr::memchr;
|
|||||||
/// of a capture group reference and is expected to resolve the index to its
|
/// of a capture group reference and is expected to resolve the index to its
|
||||||
/// corresponding matched text. If no such match exists, then `append` should
|
/// corresponding matched text. If no such match exists, then `append` should
|
||||||
/// not write anything to its given buffer.
|
/// not write anything to its given buffer.
|
||||||
|
#[inline]
|
||||||
pub fn interpolate<A, N>(
|
pub fn interpolate<A, N>(
|
||||||
mut replacement: &[u8],
|
mut replacement: &[u8],
|
||||||
mut append: A,
|
mut append: A,
|
||||||
@ -77,12 +76,14 @@ enum Ref<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a str> for Ref<'a> {
|
impl<'a> From<&'a str> for Ref<'a> {
|
||||||
|
#[inline]
|
||||||
fn from(x: &'a str) -> Ref<'a> {
|
fn from(x: &'a str) -> Ref<'a> {
|
||||||
Ref::Named(x)
|
Ref::Named(x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<usize> for Ref<'static> {
|
impl From<usize> for Ref<'static> {
|
||||||
|
#[inline]
|
||||||
fn from(x: usize) -> Ref<'static> {
|
fn from(x: usize) -> Ref<'static> {
|
||||||
Ref::Number(x)
|
Ref::Number(x)
|
||||||
}
|
}
|
||||||
@ -92,6 +93,7 @@ impl From<usize> for Ref<'static> {
|
|||||||
/// starting at the beginning of `replacement`.
|
/// starting at the beginning of `replacement`.
|
||||||
///
|
///
|
||||||
/// If no such valid reference could be found, None is returned.
|
/// If no such valid reference could be found, None is returned.
|
||||||
|
#[inline]
|
||||||
fn find_cap_ref(replacement: &[u8]) -> Option<CaptureRef<'_>> {
|
fn find_cap_ref(replacement: &[u8]) -> Option<CaptureRef<'_>> {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
if replacement.len() <= 1 || replacement[0] != b'$' {
|
if replacement.len() <= 1 || replacement[0] != b'$' {
|
||||||
@ -114,7 +116,7 @@ fn find_cap_ref(replacement: &[u8]) -> Option<CaptureRef<'_>> {
|
|||||||
// therefore be valid UTF-8. If we really cared, we could avoid this UTF-8
|
// therefore be valid UTF-8. If we really cared, we could avoid this UTF-8
|
||||||
// check with an unchecked conversion or by parsing the number straight
|
// check with an unchecked conversion or by parsing the number straight
|
||||||
// from &[u8].
|
// from &[u8].
|
||||||
let cap = str::from_utf8(&replacement[i..cap_end])
|
let cap = std::str::from_utf8(&replacement[i..cap_end])
|
||||||
.expect("valid UTF-8 capture name");
|
.expect("valid UTF-8 capture name");
|
||||||
if brace {
|
if brace {
|
||||||
if !replacement.get(cap_end).map_or(false, |&b| b == b'}') {
|
if !replacement.get(cap_end).map_or(false, |&b| b == b'}') {
|
||||||
@ -132,6 +134,7 @@ fn find_cap_ref(replacement: &[u8]) -> Option<CaptureRef<'_>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if the given byte is allowed in a capture name.
|
/// Returns true if and only if the given byte is allowed in a capture name.
|
||||||
|
#[inline]
|
||||||
fn is_valid_cap_letter(b: &u8) -> bool {
|
fn is_valid_cap_letter(b: &u8) -> bool {
|
||||||
match *b {
|
match *b {
|
||||||
b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'_' => true,
|
b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'_' => true,
|
||||||
|
@ -6,12 +6,10 @@ the search routines provided by the
|
|||||||
[`grep-searcher`](https://docs.rs/grep-searcher)
|
[`grep-searcher`](https://docs.rs/grep-searcher)
|
||||||
crate.
|
crate.
|
||||||
|
|
||||||
The primary thing provided by this crate is the
|
The primary thing provided by this crate is the [`Matcher`] trait. The trait
|
||||||
[`Matcher`](trait.Matcher.html)
|
defines an abstract interface for text search. It is robust enough to support
|
||||||
trait. The trait defines an abstract interface for text search. It is robust
|
everything from basic substring search all the way to arbitrarily complex
|
||||||
enough to support everything from basic substring search all the way to
|
regular expression implementations without sacrificing performance.
|
||||||
arbitrarily complex regular expression implementations without sacrificing
|
|
||||||
performance.
|
|
||||||
|
|
||||||
A key design decision made in this crate is the use of *internal iteration*,
|
A key design decision made in this crate is the use of *internal iteration*,
|
||||||
or otherwise known as the "push" model of searching. In this paradigm,
|
or otherwise known as the "push" model of searching. In this paradigm,
|
||||||
@ -38,11 +36,6 @@ implementations.
|
|||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use std::ops;
|
|
||||||
use std::u64;
|
|
||||||
|
|
||||||
use crate::interpolate::interpolate;
|
use crate::interpolate::interpolate;
|
||||||
|
|
||||||
mod interpolate;
|
mod interpolate;
|
||||||
@ -116,7 +109,7 @@ impl Match {
|
|||||||
/// This method panics if `start > self.end`.
|
/// This method panics if `start > self.end`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_start(&self, start: usize) -> Match {
|
pub fn with_start(&self, start: usize) -> Match {
|
||||||
assert!(start <= self.end);
|
assert!(start <= self.end, "{} is not <= {}", start, self.end);
|
||||||
Match { start, ..*self }
|
Match { start, ..*self }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +121,7 @@ impl Match {
|
|||||||
/// This method panics if `self.start > end`.
|
/// This method panics if `self.start > end`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_end(&self, end: usize) -> Match {
|
pub fn with_end(&self, end: usize) -> Match {
|
||||||
assert!(self.start <= end);
|
assert!(self.start <= end, "{} is not <= {}", self.start, end);
|
||||||
Match { end, ..*self }
|
Match { end, ..*self }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +155,7 @@ impl Match {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Index<Match> for [u8] {
|
impl std::ops::Index<Match> for [u8] {
|
||||||
type Output = [u8];
|
type Output = [u8];
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -171,14 +164,14 @@ impl ops::Index<Match> for [u8] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::IndexMut<Match> for [u8] {
|
impl std::ops::IndexMut<Match> for [u8] {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn index_mut(&mut self, index: Match) -> &mut [u8] {
|
fn index_mut(&mut self, index: Match) -> &mut [u8] {
|
||||||
&mut self[index.start..index.end]
|
&mut self[index.start..index.end]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Index<Match> for str {
|
impl std::ops::Index<Match> for str {
|
||||||
type Output = str;
|
type Output = str;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -204,11 +197,7 @@ pub struct LineTerminator(LineTerminatorImp);
|
|||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
enum LineTerminatorImp {
|
enum LineTerminatorImp {
|
||||||
/// Any single byte representing a line terminator.
|
/// Any single byte representing a line terminator.
|
||||||
///
|
Byte(u8),
|
||||||
/// We represent this as an array so we can safely convert it to a slice
|
|
||||||
/// for convenient access. At some point, we can use `std::slice::from_ref`
|
|
||||||
/// instead.
|
|
||||||
Byte([u8; 1]),
|
|
||||||
/// A line terminator represented by `\r\n`.
|
/// A line terminator represented by `\r\n`.
|
||||||
///
|
///
|
||||||
/// When this option is used, consumers may generally treat a lone `\n` as
|
/// When this option is used, consumers may generally treat a lone `\n` as
|
||||||
@ -220,7 +209,7 @@ impl LineTerminator {
|
|||||||
/// Return a new single-byte line terminator. Any byte is valid.
|
/// Return a new single-byte line terminator. Any byte is valid.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn byte(byte: u8) -> LineTerminator {
|
pub fn byte(byte: u8) -> LineTerminator {
|
||||||
LineTerminator(LineTerminatorImp::Byte([byte]))
|
LineTerminator(LineTerminatorImp::Byte(byte))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a new line terminator represented by `\r\n`.
|
/// Return a new line terminator represented by `\r\n`.
|
||||||
@ -246,7 +235,7 @@ impl LineTerminator {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_byte(&self) -> u8 {
|
pub fn as_byte(&self) -> u8 {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
LineTerminatorImp::Byte(array) => array[0],
|
LineTerminatorImp::Byte(byte) => byte,
|
||||||
LineTerminatorImp::CRLF => b'\n',
|
LineTerminatorImp::CRLF => b'\n',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,7 +249,7 @@ impl LineTerminator {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
LineTerminatorImp::Byte(ref array) => array,
|
LineTerminatorImp::Byte(ref byte) => std::slice::from_ref(byte),
|
||||||
LineTerminatorImp::CRLF => &[b'\r', b'\n'],
|
LineTerminatorImp::CRLF => &[b'\r', b'\n'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,10 +290,10 @@ pub struct ByteSet(BitSet);
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct BitSet([u64; 4]);
|
struct BitSet([u64; 4]);
|
||||||
|
|
||||||
impl fmt::Debug for BitSet {
|
impl std::fmt::Debug for BitSet {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut fmtd = f.debug_set();
|
let mut fmtd = f.debug_set();
|
||||||
for b in (0..256).map(|b| b as u8) {
|
for b in 0..=255 {
|
||||||
if ByteSet(*self).contains(b) {
|
if ByteSet(*self).contains(b) {
|
||||||
fmtd.entry(&b);
|
fmtd.entry(&b);
|
||||||
}
|
}
|
||||||
@ -315,12 +304,14 @@ impl fmt::Debug for BitSet {
|
|||||||
|
|
||||||
impl ByteSet {
|
impl ByteSet {
|
||||||
/// Create an empty set of bytes.
|
/// Create an empty set of bytes.
|
||||||
|
#[inline]
|
||||||
pub fn empty() -> ByteSet {
|
pub fn empty() -> ByteSet {
|
||||||
ByteSet(BitSet([0; 4]))
|
ByteSet(BitSet([0; 4]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a full set of bytes such that every possible byte is in the set
|
/// Create a full set of bytes such that every possible byte is in the set
|
||||||
/// returned.
|
/// returned.
|
||||||
|
#[inline]
|
||||||
pub fn full() -> ByteSet {
|
pub fn full() -> ByteSet {
|
||||||
ByteSet(BitSet([u64::MAX; 4]))
|
ByteSet(BitSet([u64::MAX; 4]))
|
||||||
}
|
}
|
||||||
@ -328,15 +319,17 @@ impl ByteSet {
|
|||||||
/// Add a byte to this set.
|
/// Add a byte to this set.
|
||||||
///
|
///
|
||||||
/// If the given byte already belongs to this set, then this is a no-op.
|
/// If the given byte already belongs to this set, then this is a no-op.
|
||||||
|
#[inline]
|
||||||
pub fn add(&mut self, byte: u8) {
|
pub fn add(&mut self, byte: u8) {
|
||||||
let bucket = byte / 64;
|
let bucket = byte / 64;
|
||||||
let bit = byte % 64;
|
let bit = byte % 64;
|
||||||
(self.0).0[bucket as usize] |= 1 << bit;
|
(self.0).0[usize::from(bucket)] |= 1 << bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an inclusive range of bytes.
|
/// Add an inclusive range of bytes.
|
||||||
|
#[inline]
|
||||||
pub fn add_all(&mut self, start: u8, end: u8) {
|
pub fn add_all(&mut self, start: u8, end: u8) {
|
||||||
for b in (start as u64..end as u64 + 1).map(|b| b as u8) {
|
for b in start..=end {
|
||||||
self.add(b);
|
self.add(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,24 +337,27 @@ impl ByteSet {
|
|||||||
/// Remove a byte from this set.
|
/// Remove a byte from this set.
|
||||||
///
|
///
|
||||||
/// If the given byte is not in this set, then this is a no-op.
|
/// If the given byte is not in this set, then this is a no-op.
|
||||||
|
#[inline]
|
||||||
pub fn remove(&mut self, byte: u8) {
|
pub fn remove(&mut self, byte: u8) {
|
||||||
let bucket = byte / 64;
|
let bucket = byte / 64;
|
||||||
let bit = byte % 64;
|
let bit = byte % 64;
|
||||||
(self.0).0[bucket as usize] &= !(1 << bit);
|
(self.0).0[usize::from(bucket)] &= !(1 << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an inclusive range of bytes.
|
/// Remove an inclusive range of bytes.
|
||||||
|
#[inline]
|
||||||
pub fn remove_all(&mut self, start: u8, end: u8) {
|
pub fn remove_all(&mut self, start: u8, end: u8) {
|
||||||
for b in (start as u64..end as u64 + 1).map(|b| b as u8) {
|
for b in start..=end {
|
||||||
self.remove(b);
|
self.remove(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if and only if the given byte is in this set.
|
/// Return true if and only if the given byte is in this set.
|
||||||
|
#[inline]
|
||||||
pub fn contains(&self, byte: u8) -> bool {
|
pub fn contains(&self, byte: u8) -> bool {
|
||||||
let bucket = byte / 64;
|
let bucket = byte / 64;
|
||||||
let bit = byte % 64;
|
let bit = byte % 64;
|
||||||
(self.0).0[bucket as usize] & (1 << bit) > 0
|
(self.0).0[usize::from(bucket)] & (1 << bit) > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,11 +389,21 @@ pub trait Captures {
|
|||||||
/// for the overall match.
|
/// for the overall match.
|
||||||
fn get(&self, i: usize) -> Option<Match>;
|
fn get(&self, i: usize) -> Option<Match>;
|
||||||
|
|
||||||
|
/// Return the overall match for the capture.
|
||||||
|
///
|
||||||
|
/// This returns the match for index `0`. That is it is equivalent to
|
||||||
|
/// `get(0).unwrap()`
|
||||||
|
#[inline]
|
||||||
|
fn as_match(&self) -> Match {
|
||||||
|
self.get(0).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if and only if these captures are empty. This occurs
|
/// Returns true if and only if these captures are empty. This occurs
|
||||||
/// when `len` is `0`.
|
/// when `len` is `0`.
|
||||||
///
|
///
|
||||||
/// Note that capturing groups that have non-zero length but otherwise
|
/// Note that capturing groups that have non-zero length but otherwise
|
||||||
/// contain no matching groups are *not* empty.
|
/// contain no matching groups are *not* empty.
|
||||||
|
#[inline]
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
@ -431,6 +437,7 @@ pub trait Captures {
|
|||||||
/// the given `haystack`. Generally, this means that `haystack` should be
|
/// the given `haystack`. Generally, this means that `haystack` should be
|
||||||
/// the same slice that was searched to get the current capture group
|
/// the same slice that was searched to get the current capture group
|
||||||
/// matches.
|
/// matches.
|
||||||
|
#[inline]
|
||||||
fn interpolate<F>(
|
fn interpolate<F>(
|
||||||
&self,
|
&self,
|
||||||
name_to_index: F,
|
name_to_index: F,
|
||||||
@ -462,15 +469,19 @@ pub struct NoCaptures(());
|
|||||||
|
|
||||||
impl NoCaptures {
|
impl NoCaptures {
|
||||||
/// Create an empty set of capturing groups.
|
/// Create an empty set of capturing groups.
|
||||||
|
#[inline]
|
||||||
pub fn new() -> NoCaptures {
|
pub fn new() -> NoCaptures {
|
||||||
NoCaptures(())
|
NoCaptures(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Captures for NoCaptures {
|
impl Captures for NoCaptures {
|
||||||
|
#[inline]
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn get(&self, _: usize) -> Option<Match> {
|
fn get(&self, _: usize) -> Option<Match> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -478,27 +489,27 @@ impl Captures for NoCaptures {
|
|||||||
|
|
||||||
/// NoError provides an error type for matchers that never produce errors.
|
/// NoError provides an error type for matchers that never produce errors.
|
||||||
///
|
///
|
||||||
/// This error type implements the `std::error::Error` and `fmt::Display`
|
/// This error type implements the `std::error::Error` and `std::fmt::Display`
|
||||||
/// traits for use in matcher implementations that can never produce errors.
|
/// traits for use in matcher implementations that can never produce errors.
|
||||||
///
|
///
|
||||||
/// The `fmt::Debug` and `fmt::Display` impls for this type panics.
|
/// The `std::fmt::Debug` and `std::fmt::Display` impls for this type panics.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct NoError(());
|
pub struct NoError(());
|
||||||
|
|
||||||
impl ::std::error::Error for NoError {
|
impl std::error::Error for NoError {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"no error"
|
"no error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for NoError {
|
impl std::fmt::Display for NoError {
|
||||||
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
panic!("BUG for NoError: an impossible error occurred")
|
panic!("BUG for NoError: an impossible error occurred")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NoError> for io::Error {
|
impl From<NoError> for std::io::Error {
|
||||||
fn from(_: NoError) -> io::Error {
|
fn from(_: NoError) -> std::io::Error {
|
||||||
panic!("BUG for NoError: an impossible error occurred")
|
panic!("BUG for NoError: an impossible error occurred")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -522,13 +533,11 @@ pub enum LineMatchKind {
|
|||||||
/// A matcher defines an interface for regular expression implementations.
|
/// A matcher defines an interface for regular expression implementations.
|
||||||
///
|
///
|
||||||
/// While this trait is large, there are only two required methods that
|
/// While this trait is large, there are only two required methods that
|
||||||
/// implementors must provide: `find_at` and `new_captures`. If captures
|
/// implementors must provide: `find_at` and `new_captures`. If captures aren't
|
||||||
/// aren't supported by your implementation, then `new_captures` can be
|
/// supported by your implementation, then `new_captures` can be implemented
|
||||||
/// implemented with
|
/// with [`NoCaptures`]. If your implementation does support capture groups,
|
||||||
/// [`NoCaptures`](struct.NoCaptures.html). If your implementation does support
|
/// then you should also implement the other capture related methods, as
|
||||||
/// capture groups, then you should also implement the other capture related
|
/// dictated by the documentation. Crucially, this includes `captures_at`.
|
||||||
/// methods, as dictated by the documentation. Crucially, this includes
|
|
||||||
/// `captures_at`.
|
|
||||||
///
|
///
|
||||||
/// The rest of the methods on this trait provide default implementations on
|
/// The rest of the methods on this trait provide default implementations on
|
||||||
/// top of `find_at` and `new_captures`. It is not uncommon for implementations
|
/// top of `find_at` and `new_captures`. It is not uncommon for implementations
|
||||||
@ -547,7 +556,7 @@ pub trait Matcher {
|
|||||||
/// use the `NoError` type in this crate. In the future, when the "never"
|
/// use the `NoError` type in this crate. In the future, when the "never"
|
||||||
/// (spelled `!`) type is stabilized, then it should probably be used
|
/// (spelled `!`) type is stabilized, then it should probably be used
|
||||||
/// instead.
|
/// instead.
|
||||||
type Error: fmt::Display;
|
type Error: std::fmt::Display;
|
||||||
|
|
||||||
/// Returns the start and end byte range of the first match in `haystack`
|
/// Returns the start and end byte range of the first match in `haystack`
|
||||||
/// after `at`, where the byte offsets are relative to that start of
|
/// after `at`, where the byte offsets are relative to that start of
|
||||||
@ -584,6 +593,7 @@ pub trait Matcher {
|
|||||||
///
|
///
|
||||||
/// By default, capturing groups are not supported, so this always
|
/// By default, capturing groups are not supported, so this always
|
||||||
/// returns 0.
|
/// returns 0.
|
||||||
|
#[inline]
|
||||||
fn capture_count(&self) -> usize {
|
fn capture_count(&self) -> usize {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@ -597,6 +607,7 @@ pub trait Matcher {
|
|||||||
///
|
///
|
||||||
/// By default, capturing groups are not supported, so this always returns
|
/// By default, capturing groups are not supported, so this always returns
|
||||||
/// `None`.
|
/// `None`.
|
||||||
|
#[inline]
|
||||||
fn capture_index(&self, _name: &str) -> Option<usize> {
|
fn capture_index(&self, _name: &str) -> Option<usize> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -606,6 +617,7 @@ pub trait Matcher {
|
|||||||
///
|
///
|
||||||
/// The text encoding of `haystack` is not strictly specified. Matchers are
|
/// The text encoding of `haystack` is not strictly specified. Matchers are
|
||||||
/// advised to assume UTF-8, or at worst, some ASCII compatible encoding.
|
/// advised to assume UTF-8, or at worst, some ASCII compatible encoding.
|
||||||
|
#[inline]
|
||||||
fn find(&self, haystack: &[u8]) -> Result<Option<Match>, Self::Error> {
|
fn find(&self, haystack: &[u8]) -> Result<Option<Match>, Self::Error> {
|
||||||
self.find_at(haystack, 0)
|
self.find_at(haystack, 0)
|
||||||
}
|
}
|
||||||
@ -613,6 +625,7 @@ pub trait Matcher {
|
|||||||
/// Executes the given function over successive non-overlapping matches
|
/// Executes the given function over successive non-overlapping matches
|
||||||
/// in `haystack`. If no match exists, then the given function is never
|
/// in `haystack`. If no match exists, then the given function is never
|
||||||
/// called. If the function returns `false`, then iteration stops.
|
/// called. If the function returns `false`, then iteration stops.
|
||||||
|
#[inline]
|
||||||
fn find_iter<F>(
|
fn find_iter<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -631,6 +644,7 @@ pub trait Matcher {
|
|||||||
/// The significance of the starting point is that it takes the surrounding
|
/// The significance of the starting point is that it takes the surrounding
|
||||||
/// context into consideration. For example, the `\A` anchor can only
|
/// context into consideration. For example, the `\A` anchor can only
|
||||||
/// match when `at == 0`.
|
/// match when `at == 0`.
|
||||||
|
#[inline]
|
||||||
fn find_iter_at<F>(
|
fn find_iter_at<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -651,6 +665,7 @@ pub trait Matcher {
|
|||||||
/// the error is yielded. If an error occurs while executing the search,
|
/// the error is yielded. If an error occurs while executing the search,
|
||||||
/// then it is converted to
|
/// then it is converted to
|
||||||
/// `E`.
|
/// `E`.
|
||||||
|
#[inline]
|
||||||
fn try_find_iter<F, E>(
|
fn try_find_iter<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -673,6 +688,7 @@ pub trait Matcher {
|
|||||||
/// The significance of the starting point is that it takes the surrounding
|
/// The significance of the starting point is that it takes the surrounding
|
||||||
/// context into consideration. For example, the `\A` anchor can only
|
/// context into consideration. For example, the `\A` anchor can only
|
||||||
/// match when `at == 0`.
|
/// match when `at == 0`.
|
||||||
|
#[inline]
|
||||||
fn try_find_iter_at<F, E>(
|
fn try_find_iter_at<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -720,6 +736,7 @@ pub trait Matcher {
|
|||||||
///
|
///
|
||||||
/// The text encoding of `haystack` is not strictly specified. Matchers are
|
/// The text encoding of `haystack` is not strictly specified. Matchers are
|
||||||
/// advised to assume UTF-8, or at worst, some ASCII compatible encoding.
|
/// advised to assume UTF-8, or at worst, some ASCII compatible encoding.
|
||||||
|
#[inline]
|
||||||
fn captures(
|
fn captures(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -732,6 +749,7 @@ pub trait Matcher {
|
|||||||
/// in `haystack` with capture groups extracted from each match. If no
|
/// in `haystack` with capture groups extracted from each match. If no
|
||||||
/// match exists, then the given function is never called. If the function
|
/// match exists, then the given function is never called. If the function
|
||||||
/// returns `false`, then iteration stops.
|
/// returns `false`, then iteration stops.
|
||||||
|
#[inline]
|
||||||
fn captures_iter<F>(
|
fn captures_iter<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -752,6 +770,7 @@ pub trait Matcher {
|
|||||||
/// The significance of the starting point is that it takes the surrounding
|
/// The significance of the starting point is that it takes the surrounding
|
||||||
/// context into consideration. For example, the `\A` anchor can only
|
/// context into consideration. For example, the `\A` anchor can only
|
||||||
/// match when `at == 0`.
|
/// match when `at == 0`.
|
||||||
|
#[inline]
|
||||||
fn captures_iter_at<F>(
|
fn captures_iter_at<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -773,6 +792,7 @@ pub trait Matcher {
|
|||||||
/// returns an error then iteration stops and the error is yielded. If
|
/// returns an error then iteration stops and the error is yielded. If
|
||||||
/// an error occurs while executing the search, then it is converted to
|
/// an error occurs while executing the search, then it is converted to
|
||||||
/// `E`.
|
/// `E`.
|
||||||
|
#[inline]
|
||||||
fn try_captures_iter<F, E>(
|
fn try_captures_iter<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -796,6 +816,7 @@ pub trait Matcher {
|
|||||||
/// The significance of the starting point is that it takes the surrounding
|
/// The significance of the starting point is that it takes the surrounding
|
||||||
/// context into consideration. For example, the `\A` anchor can only
|
/// context into consideration. For example, the `\A` anchor can only
|
||||||
/// match when `at == 0`.
|
/// match when `at == 0`.
|
||||||
|
#[inline]
|
||||||
fn try_captures_iter_at<F, E>(
|
fn try_captures_iter_at<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -862,6 +883,7 @@ pub trait Matcher {
|
|||||||
/// Note that if implementors seek to support capturing groups, then they
|
/// Note that if implementors seek to support capturing groups, then they
|
||||||
/// should implement this method. Other methods that match based on
|
/// should implement this method. Other methods that match based on
|
||||||
/// captures will then work automatically.
|
/// captures will then work automatically.
|
||||||
|
#[inline]
|
||||||
fn captures_at(
|
fn captures_at(
|
||||||
&self,
|
&self,
|
||||||
_haystack: &[u8],
|
_haystack: &[u8],
|
||||||
@ -876,6 +898,7 @@ pub trait Matcher {
|
|||||||
/// a handle to the `dst` buffer provided.
|
/// a handle to the `dst` buffer provided.
|
||||||
///
|
///
|
||||||
/// If the given `append` function returns `false`, then replacement stops.
|
/// If the given `append` function returns `false`, then replacement stops.
|
||||||
|
#[inline]
|
||||||
fn replace<F>(
|
fn replace<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -899,6 +922,7 @@ pub trait Matcher {
|
|||||||
/// `append` with the matching capture groups.
|
/// `append` with the matching capture groups.
|
||||||
///
|
///
|
||||||
/// If the given `append` function returns `false`, then replacement stops.
|
/// If the given `append` function returns `false`, then replacement stops.
|
||||||
|
#[inline]
|
||||||
fn replace_with_captures<F>(
|
fn replace_with_captures<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -920,6 +944,7 @@ pub trait Matcher {
|
|||||||
/// The significance of the starting point is that it takes the surrounding
|
/// The significance of the starting point is that it takes the surrounding
|
||||||
/// context into consideration. For example, the `\A` anchor can only
|
/// context into consideration. For example, the `\A` anchor can only
|
||||||
/// match when `at == 0`.
|
/// match when `at == 0`.
|
||||||
|
#[inline]
|
||||||
fn replace_with_captures_at<F>(
|
fn replace_with_captures_at<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -945,6 +970,7 @@ pub trait Matcher {
|
|||||||
/// Returns true if and only if the matcher matches the given haystack.
|
/// Returns true if and only if the matcher matches the given haystack.
|
||||||
///
|
///
|
||||||
/// By default, this method is implemented by calling `shortest_match`.
|
/// By default, this method is implemented by calling `shortest_match`.
|
||||||
|
#[inline]
|
||||||
fn is_match(&self, haystack: &[u8]) -> Result<bool, Self::Error> {
|
fn is_match(&self, haystack: &[u8]) -> Result<bool, Self::Error> {
|
||||||
self.is_match_at(haystack, 0)
|
self.is_match_at(haystack, 0)
|
||||||
}
|
}
|
||||||
@ -957,6 +983,7 @@ pub trait Matcher {
|
|||||||
/// The significance of the starting point is that it takes the surrounding
|
/// The significance of the starting point is that it takes the surrounding
|
||||||
/// context into consideration. For example, the `\A` anchor can only
|
/// context into consideration. For example, the `\A` anchor can only
|
||||||
/// match when `at == 0`.
|
/// match when `at == 0`.
|
||||||
|
#[inline]
|
||||||
fn is_match_at(
|
fn is_match_at(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -979,6 +1006,7 @@ pub trait Matcher {
|
|||||||
/// a faster implementation of this than what `find` does.
|
/// a faster implementation of this than what `find` does.
|
||||||
///
|
///
|
||||||
/// By default, this method is implemented by calling `find`.
|
/// By default, this method is implemented by calling `find`.
|
||||||
|
#[inline]
|
||||||
fn shortest_match(
|
fn shortest_match(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1004,6 +1032,7 @@ pub trait Matcher {
|
|||||||
/// The significance of the starting point is that it takes the surrounding
|
/// The significance of the starting point is that it takes the surrounding
|
||||||
/// context into consideration. For example, the `\A` anchor can only
|
/// context into consideration. For example, the `\A` anchor can only
|
||||||
/// match when `at == 0`.
|
/// match when `at == 0`.
|
||||||
|
#[inline]
|
||||||
fn shortest_match_at(
|
fn shortest_match_at(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1032,6 +1061,7 @@ pub trait Matcher {
|
|||||||
/// exists with that byte.
|
/// exists with that byte.
|
||||||
///
|
///
|
||||||
/// By default, this returns `None`.
|
/// By default, this returns `None`.
|
||||||
|
#[inline]
|
||||||
fn non_matching_bytes(&self) -> Option<&ByteSet> {
|
fn non_matching_bytes(&self) -> Option<&ByteSet> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -1048,6 +1078,7 @@ pub trait Matcher {
|
|||||||
/// `CRLF`.
|
/// `CRLF`.
|
||||||
///
|
///
|
||||||
/// By default, this returns `None`.
|
/// By default, this returns `None`.
|
||||||
|
#[inline]
|
||||||
fn line_terminator(&self) -> Option<LineTerminator> {
|
fn line_terminator(&self) -> Option<LineTerminator> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -1090,6 +1121,7 @@ pub trait Matcher {
|
|||||||
/// Note that while this method may report false positives, it must never
|
/// Note that while this method may report false positives, it must never
|
||||||
/// report false negatives. That is, it can never skip over lines that
|
/// report false negatives. That is, it can never skip over lines that
|
||||||
/// contain a match.
|
/// contain a match.
|
||||||
|
#[inline]
|
||||||
fn find_candidate_line(
|
fn find_candidate_line(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1102,6 +1134,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
type Captures = M::Captures;
|
type Captures = M::Captures;
|
||||||
type Error = M::Error;
|
type Error = M::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn find_at(
|
fn find_at(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1110,10 +1143,12 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).find_at(haystack, at)
|
(*self).find_at(haystack, at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn new_captures(&self) -> Result<Self::Captures, Self::Error> {
|
fn new_captures(&self) -> Result<Self::Captures, Self::Error> {
|
||||||
(*self).new_captures()
|
(*self).new_captures()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn captures_at(
|
fn captures_at(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1123,18 +1158,22 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).captures_at(haystack, at, caps)
|
(*self).captures_at(haystack, at, caps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn capture_index(&self, name: &str) -> Option<usize> {
|
fn capture_index(&self, name: &str) -> Option<usize> {
|
||||||
(*self).capture_index(name)
|
(*self).capture_index(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn capture_count(&self) -> usize {
|
fn capture_count(&self) -> usize {
|
||||||
(*self).capture_count()
|
(*self).capture_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn find(&self, haystack: &[u8]) -> Result<Option<Match>, Self::Error> {
|
fn find(&self, haystack: &[u8]) -> Result<Option<Match>, Self::Error> {
|
||||||
(*self).find(haystack)
|
(*self).find(haystack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn find_iter<F>(
|
fn find_iter<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1146,6 +1185,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).find_iter(haystack, matched)
|
(*self).find_iter(haystack, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn find_iter_at<F>(
|
fn find_iter_at<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1158,6 +1198,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).find_iter_at(haystack, at, matched)
|
(*self).find_iter_at(haystack, at, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn try_find_iter<F, E>(
|
fn try_find_iter<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1169,6 +1210,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).try_find_iter(haystack, matched)
|
(*self).try_find_iter(haystack, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn try_find_iter_at<F, E>(
|
fn try_find_iter_at<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1181,6 +1223,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).try_find_iter_at(haystack, at, matched)
|
(*self).try_find_iter_at(haystack, at, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn captures(
|
fn captures(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1189,6 +1232,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).captures(haystack, caps)
|
(*self).captures(haystack, caps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn captures_iter<F>(
|
fn captures_iter<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1201,6 +1245,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).captures_iter(haystack, caps, matched)
|
(*self).captures_iter(haystack, caps, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn captures_iter_at<F>(
|
fn captures_iter_at<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1214,6 +1259,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).captures_iter_at(haystack, at, caps, matched)
|
(*self).captures_iter_at(haystack, at, caps, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn try_captures_iter<F, E>(
|
fn try_captures_iter<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1226,6 +1272,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).try_captures_iter(haystack, caps, matched)
|
(*self).try_captures_iter(haystack, caps, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn try_captures_iter_at<F, E>(
|
fn try_captures_iter_at<F, E>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1239,6 +1286,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).try_captures_iter_at(haystack, at, caps, matched)
|
(*self).try_captures_iter_at(haystack, at, caps, matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn replace<F>(
|
fn replace<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1251,6 +1299,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).replace(haystack, dst, append)
|
(*self).replace(haystack, dst, append)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn replace_with_captures<F>(
|
fn replace_with_captures<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1264,6 +1313,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).replace_with_captures(haystack, caps, dst, append)
|
(*self).replace_with_captures(haystack, caps, dst, append)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn replace_with_captures_at<F>(
|
fn replace_with_captures_at<F>(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1278,10 +1328,12 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).replace_with_captures_at(haystack, at, caps, dst, append)
|
(*self).replace_with_captures_at(haystack, at, caps, dst, append)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn is_match(&self, haystack: &[u8]) -> Result<bool, Self::Error> {
|
fn is_match(&self, haystack: &[u8]) -> Result<bool, Self::Error> {
|
||||||
(*self).is_match(haystack)
|
(*self).is_match(haystack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn is_match_at(
|
fn is_match_at(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1290,6 +1342,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).is_match_at(haystack, at)
|
(*self).is_match_at(haystack, at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn shortest_match(
|
fn shortest_match(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1297,6 +1350,7 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).shortest_match(haystack)
|
(*self).shortest_match(haystack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn shortest_match_at(
|
fn shortest_match_at(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
@ -1305,14 +1359,17 @@ impl<'a, M: Matcher> Matcher for &'a M {
|
|||||||
(*self).shortest_match_at(haystack, at)
|
(*self).shortest_match_at(haystack, at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn non_matching_bytes(&self) -> Option<&ByteSet> {
|
fn non_matching_bytes(&self) -> Option<&ByteSet> {
|
||||||
(*self).non_matching_bytes()
|
(*self).non_matching_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn line_terminator(&self) -> Option<LineTerminator> {
|
fn line_terminator(&self) -> Option<LineTerminator> {
|
||||||
(*self).line_terminator()
|
(*self).line_terminator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn find_candidate_line(
|
fn find_candidate_line(
|
||||||
&self,
|
&self,
|
||||||
haystack: &[u8],
|
haystack: &[u8],
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use grep_matcher::{Captures, Match, Matcher};
|
use {
|
||||||
use regex::bytes::Regex;
|
grep_matcher::{Captures, Match, Matcher},
|
||||||
|
regex::bytes::Regex,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::util::{RegexMatcher, RegexMatcherNoCaps};
|
use crate::util::{RegexMatcher, RegexMatcherNoCaps};
|
||||||
|
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::result;
|
|
||||||
|
|
||||||
use grep_matcher::{Captures, Match, Matcher, NoCaptures, NoError};
|
use {
|
||||||
use regex::bytes::{CaptureLocations, Regex};
|
grep_matcher::{Captures, Match, Matcher, NoCaptures, NoError},
|
||||||
|
regex::bytes::{CaptureLocations, Regex},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RegexMatcher {
|
pub(crate) struct RegexMatcher {
|
||||||
pub re: Regex,
|
pub re: Regex,
|
||||||
pub names: HashMap<String, usize>,
|
pub names: HashMap<String, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegexMatcher {
|
impl RegexMatcher {
|
||||||
pub fn new(re: Regex) -> RegexMatcher {
|
pub(crate) fn new(re: Regex) -> RegexMatcher {
|
||||||
let mut names = HashMap::new();
|
let mut names = HashMap::new();
|
||||||
for (i, optional_name) in re.capture_names().enumerate() {
|
for (i, optional_name) in re.capture_names().enumerate() {
|
||||||
if let Some(name) = optional_name {
|
if let Some(name) = optional_name {
|
||||||
names.insert(name.to_string(), i);
|
names.insert(name.to_string(), i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RegexMatcher { re: re, names: names }
|
RegexMatcher { re, names }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, NoError>;
|
type Result<T> = std::result::Result<T, NoError>;
|
||||||
|
|
||||||
impl Matcher for RegexMatcher {
|
impl Matcher for RegexMatcher {
|
||||||
type Captures = RegexCaptures;
|
type Captures = RegexCaptures;
|
||||||
@ -63,7 +64,7 @@ impl Matcher for RegexMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RegexMatcherNoCaps(pub Regex);
|
pub(crate) struct RegexMatcherNoCaps(pub(crate) Regex);
|
||||||
|
|
||||||
impl Matcher for RegexMatcherNoCaps {
|
impl Matcher for RegexMatcherNoCaps {
|
||||||
type Captures = NoCaptures;
|
type Captures = NoCaptures;
|
||||||
@ -82,7 +83,7 @@ impl Matcher for RegexMatcherNoCaps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RegexCaptures(CaptureLocations);
|
pub(crate) struct RegexCaptures(CaptureLocations);
|
||||||
|
|
||||||
impl Captures for RegexCaptures {
|
impl Captures for RegexCaptures {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-pcre2"
|
name = "grep-pcre2"
|
||||||
version = "0.1.5" #:version
|
version = "0.1.8" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Use PCRE2 with the 'grep' crate.
|
Use PCRE2 with the 'grep' crate.
|
||||||
@ -10,9 +10,10 @@ homepage = "https://github.com/BurntSushi/ripgrep/tree/master/crates/pcre2"
|
|||||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/pcre2"
|
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/pcre2"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["regex", "grep", "pcre", "backreference", "look"]
|
keywords = ["regex", "grep", "pcre", "backreference", "look"]
|
||||||
license = "Unlicense/MIT"
|
license = "Unlicense OR MIT"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
grep-matcher = { version = "0.1.5", path = "../matcher" }
|
grep-matcher = { version = "0.1.7", path = "../matcher" }
|
||||||
pcre2 = "0.2.3"
|
log = "0.4.20"
|
||||||
|
pcre2 = "0.2.6"
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// An error that can occur in this crate.
|
/// An error that can occur in this crate.
|
||||||
///
|
///
|
||||||
/// Generally, this error corresponds to problems building a regular
|
/// Generally, this error corresponds to problems building a regular
|
||||||
@ -12,7 +9,7 @@ pub struct Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub(crate) fn regex<E: error::Error>(err: E) -> Error {
|
pub(crate) fn regex<E: std::error::Error>(err: E) -> Error {
|
||||||
Error { kind: ErrorKind::Regex(err.to_string()) }
|
Error { kind: ErrorKind::Regex(err.to_string()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +21,7 @@ impl Error {
|
|||||||
|
|
||||||
/// The kind of an error that can occur.
|
/// The kind of an error that can occur.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
/// An error that occurred as a result of parsing a regular expression.
|
/// An error that occurred as a result of parsing a regular expression.
|
||||||
/// This can be a syntax error or an error that results from attempting to
|
/// This can be a syntax error or an error that results from attempting to
|
||||||
@ -31,29 +29,20 @@ pub enum ErrorKind {
|
|||||||
///
|
///
|
||||||
/// The string here is the underlying error converted to a string.
|
/// The string here is the underlying error converted to a string.
|
||||||
Regex(String),
|
Regex(String),
|
||||||
/// Hints that destructuring should not be exhaustive.
|
|
||||||
///
|
|
||||||
/// This enum may grow additional variants, so this makes sure clients
|
|
||||||
/// don't count on exhaustive matching. (Otherwise, adding a new variant
|
|
||||||
/// could break existing code.)
|
|
||||||
#[doc(hidden)]
|
|
||||||
__Nonexhaustive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for Error {
|
impl std::error::Error for Error {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
ErrorKind::Regex(_) => "regex error",
|
ErrorKind::Regex(_) => "regex error",
|
||||||
ErrorKind::__Nonexhaustive => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
ErrorKind::Regex(ref s) => write!(f, "{}", s),
|
ErrorKind::Regex(ref s) => write!(f, "{}", s),
|
||||||
ErrorKind::__Nonexhaustive => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user