mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-08-15 04:05:46 -07:00
Compare commits
49 Commits
grep-cli-0
...
globset-0.
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -1,6 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
52
.github/workflows/release.yml
vendored
52
.github/workflows/release.yml
vendored
@@ -24,31 +24,24 @@ on:
|
||||
jobs:
|
||||
create-release:
|
||||
name: create-release
|
||||
runs-on: ubuntu-22.04
|
||||
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:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get the release version from the tag
|
||||
shell: bash
|
||||
if: env.RG_VERSION == ''
|
||||
run: |
|
||||
# Apparently, this is the right way to get a tag name. Really?
|
||||
#
|
||||
# See: https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
||||
echo "RG_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
echo "RG_VERSION=$GITHUB_REF_NAME" >> $GITHUB_ENV
|
||||
echo "version is: ${{ env.RG_VERSION }}"
|
||||
- name: Create GitHub release
|
||||
id: release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.RG_VERSION }}
|
||||
release_name: ${{ env.RG_VERSION }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh release create ${{ env.RG_VERSION }}
|
||||
|
||||
build-release:
|
||||
name: build-release
|
||||
@@ -71,27 +64,27 @@ jobs:
|
||||
build: [linux, linux-arm, macos, win-msvc, win-gnu, win32-msvc]
|
||||
include:
|
||||
- build: linux
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-latest
|
||||
rust: nightly
|
||||
target: x86_64-unknown-linux-musl
|
||||
- build: linux-arm
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-latest
|
||||
rust: nightly
|
||||
target: arm-unknown-linux-gnueabihf
|
||||
- build: macos
|
||||
os: macos-12
|
||||
os: macos-latest
|
||||
rust: nightly
|
||||
target: x86_64-apple-darwin
|
||||
- build: win-msvc
|
||||
os: windows-2022
|
||||
os: windows-latest
|
||||
rust: nightly
|
||||
target: x86_64-pc-windows-msvc
|
||||
- build: win-gnu
|
||||
os: windows-2022
|
||||
os: windows-latest
|
||||
rust: nightly-x86_64-gnu
|
||||
target: x86_64-pc-windows-gnu
|
||||
- build: win32-msvc
|
||||
os: windows-2022
|
||||
os: windows-latest
|
||||
rust: nightly
|
||||
target: i686-pc-windows-msvc
|
||||
|
||||
@@ -100,12 +93,12 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install packages (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
ci/ubuntu-install-packages
|
||||
|
||||
- name: Install packages (macOS)
|
||||
if: matrix.os == 'macos-12'
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
ci/macos-install-packages
|
||||
|
||||
@@ -132,8 +125,8 @@ jobs:
|
||||
- name: Build release binary
|
||||
run: ${{ env.CARGO }} build --verbose --release --features pcre2 ${{ env.TARGET_FLAGS }}
|
||||
|
||||
- name: Strip release binary (linux and macos)
|
||||
if: matrix.build == 'linux' || matrix.build == 'macos'
|
||||
- name: Strip release binary (linux, macos and macos-arm)
|
||||
if: matrix.build == 'linux' || matrix.os == 'macos'
|
||||
run: strip "target/${{ matrix.target }}/release/rg"
|
||||
|
||||
- name: Strip release binary (arm)
|
||||
@@ -157,24 +150,23 @@ jobs:
|
||||
cp "$outdir"/{rg.bash,rg.fish,_rg.ps1} "$staging/complete/"
|
||||
cp complete/_rg "$staging/complete/"
|
||||
|
||||
if [ "${{ matrix.os }}" = "windows-2022" ]; then
|
||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||
cp "target/${{ matrix.target }}/release/rg.exe" "$staging/"
|
||||
7z a "$staging.zip" "$staging"
|
||||
certutil -hashfile "$staging.zip" SHA256 > "$staging.zip.sha256"
|
||||
echo "ASSET=$staging.zip" >> $GITHUB_ENV
|
||||
echo "ASSET_SUM=$staging.zip.sha256" >> $GITHUB_ENV
|
||||
else
|
||||
# The man page is only generated on Unix systems. ¯\_(ツ)_/¯
|
||||
cp "$outdir"/rg.1 "$staging/doc/"
|
||||
cp "target/${{ matrix.target }}/release/rg" "$staging/"
|
||||
tar czf "$staging.tar.gz" "$staging"
|
||||
shasum -a 256 "$staging.tar.gz" > "$staging.tar.gz.sha256"
|
||||
echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
|
||||
echo "ASSET_SUM=$staging.tar.gz.sha256" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Upload release archive
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.ASSET }}
|
||||
asset_name: ${{ env.ASSET }}
|
||||
asset_content_type: application/octet-stream
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh release upload ${{ needs.create-release.outputs.rg_version }} ${{ env.ASSET }} ${{ env.ASSET_SUM }}
|
||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@@ -2,14 +2,42 @@ TBD
|
||||
===
|
||||
Unreleased changes. Release notes have not yet been written.
|
||||
|
||||
**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`.
|
||||
|
||||
Feature enhancements:
|
||||
|
||||
* Added or improved file type filtering for Ada, DITA, Elixir, Fuchsia, Gentoo, GraphQL, Markdown, Raku, TypeScript, USD, V
|
||||
* [FEATURE #1790](https://github.com/BurntSushi/ripgrep/issues/1790):
|
||||
Add new `--stop-on-nonmatch` flag.
|
||||
* [FEATURE #2195](https://github.com/BurntSushi/ripgrep/issues/2195):
|
||||
When `extra-verbose` mode is enabled in zsh, show extra file type info.
|
||||
* [FEATURE #2409](https://github.com/BurntSushi/ripgrep/pull/2409):
|
||||
Added installation instructions for `winget`.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* [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 #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 #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 #2480](https://github.com/BurntSushi/ripgrep/issues/2480):
|
||||
Fix bug when using inline regex flags with `-e/--regexp`.
|
||||
* [BUG #2523](https://github.com/BurntSushi/ripgrep/issues/2523):
|
||||
Make executable searching take `.com` into account on Windows.
|
||||
|
||||
|
||||
13.0.0 (2021-06-12)
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -119,7 +119,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
|
11
Cargo.toml
11
Cargo.toml
@@ -13,11 +13,18 @@ repository = "https://github.com/BurntSushi/ripgrep"
|
||||
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
||||
categories = ["command-line-utilities", "text-processing"]
|
||||
license = "Unlicense OR MIT"
|
||||
exclude = ["HomebrewFormula"]
|
||||
exclude = [
|
||||
"HomebrewFormula",
|
||||
"/.github/",
|
||||
"/ci/",
|
||||
"/pkg/",
|
||||
"/benchsuite/",
|
||||
"/scripts/",
|
||||
]
|
||||
build = "build.rs"
|
||||
autotests = false
|
||||
edition = "2018"
|
||||
rust-version = "1.65"
|
||||
rust-version = "1.70"
|
||||
|
||||
[[bin]]
|
||||
bench = false
|
||||
|
7
GUIDE.md
7
GUIDE.md
@@ -567,12 +567,15 @@ $ cat $HOME/.ripgreprc
|
||||
--type-add
|
||||
web:*.{html,css,js}*
|
||||
|
||||
# Search hidden files / directories (e.g. dotfiles) by default
|
||||
--hidden
|
||||
|
||||
# Using glob patterns to include/exclude files or folders
|
||||
--glob=!git/*
|
||||
--glob=!.git/*
|
||||
|
||||
# or
|
||||
--glob
|
||||
!git/*
|
||||
!.git/*
|
||||
|
||||
# Set the colors.
|
||||
--colors=line:none
|
||||
|
39
README.md
39
README.md
@@ -228,17 +228,25 @@ If you're a **Windows Scoop** user, then you can install ripgrep from the
|
||||
$ 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:
|
||||
|
||||
```
|
||||
$ pacman -S ripgrep
|
||||
$ sudo pacman -S ripgrep
|
||||
```
|
||||
|
||||
If you're a **Gentoo** user, you can install ripgrep from the
|
||||
[official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
|
||||
|
||||
```
|
||||
$ emerge sys-apps/ripgrep
|
||||
$ sudo emerge sys-apps/ripgrep
|
||||
```
|
||||
|
||||
If you're a **Fedora** user, you can install ripgrep from official
|
||||
@@ -259,6 +267,7 @@ If you're a **RHEL/CentOS 7/8** user, you can install ripgrep from
|
||||
[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 install ripgrep
|
||||
```
|
||||
@@ -268,14 +277,13 @@ If you're a **Nix** user, you can install ripgrep from
|
||||
|
||||
```
|
||||
$ nix-env --install ripgrep
|
||||
$ # (Or using the attribute name, which is also ripgrep.)
|
||||
```
|
||||
|
||||
If you're a **Guix** user, you can install ripgrep from the official
|
||||
package collection:
|
||||
|
||||
```
|
||||
$ guix install ripgrep
|
||||
$ sudo guix install ripgrep
|
||||
```
|
||||
|
||||
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
||||
@@ -308,11 +316,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
|
||||
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
|
||||
[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
|
||||
@@ -326,21 +341,21 @@ If you're a **NetBSD** user, then you can install ripgrep from
|
||||
[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
|
||||
[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
|
||||
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 **Rust programmer**, ripgrep can be installed with `cargo`.
|
||||
@@ -360,7 +375,7 @@ $ cargo install ripgrep
|
||||
|
||||
ripgrep is written in Rust, so you'll need to grab a
|
||||
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
||||
ripgrep compiles with Rust 1.65.0 (stable) or newer. In general, ripgrep tracks
|
||||
ripgrep compiles with Rust 1.70.0 (stable) or newer. In general, ripgrep tracks
|
||||
the latest stable release of the Rust compiler.
|
||||
|
||||
To build ripgrep:
|
||||
@@ -443,9 +458,9 @@ grep](https://dandavison.github.io/delta/grep.html) for more details.
|
||||
### Vulnerability reporting
|
||||
|
||||
For reporting a security vulnerability, please
|
||||
[contact Andrew Gallant](https://blog.burntsushi.net/about/),
|
||||
which has my email address and PGP public key if you wish to send an encrypted
|
||||
message.
|
||||
[contact Andrew Gallant](https://blog.burntsushi.net/about/).
|
||||
The contact page has my email address and PGP public key if you wish to send an
|
||||
encrypted message.
|
||||
|
||||
|
||||
### Translations
|
||||
|
28
build.rs
28
build.rs
@@ -48,6 +48,34 @@ fn main() {
|
||||
if let Some(rev) = git_revision_hash() {
|
||||
println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", rev);
|
||||
}
|
||||
// Embed a Windows manifest and set some linker options. The main reason
|
||||
// for this is to enable long path support on Windows. This still, I
|
||||
// believe, requires enabling long path support in the registry. But if
|
||||
// that's enabled, then this will let ripgrep use C:\... style paths that
|
||||
// are longer than 260 characters.
|
||||
set_windows_exe_options();
|
||||
}
|
||||
|
||||
fn set_windows_exe_options() {
|
||||
static MANIFEST: &str = "pkg/windows/Manifest.xml";
|
||||
|
||||
let Ok(target_os) = env::var("CARGO_CFG_TARGET_OS") else { return };
|
||||
let Ok(target_env) = env::var("CARGO_CFG_TARGET_ENV") else { return };
|
||||
if !(target_os == "windows" && target_env == "msvc") {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(mut manifest) = 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 git_revision_hash() -> Option<String> {
|
||||
|
11
complete/_rg
11
complete/_rg
@@ -30,7 +30,7 @@ _rg() {
|
||||
[[ $_RG_COMPLETE_LIST_ARGS == (1|t*|y*) ]] ||
|
||||
# (--[imnp]* => --ignore*, --messages, --no-*, --pcre2-unicode)
|
||||
[[ $PREFIX$SUFFIX == --[imnp]* ]] ||
|
||||
zstyle -t ":complete:$curcontext:*" complete-all
|
||||
zstyle -t ":completion:${curcontext}:" complete-all
|
||||
then
|
||||
no=
|
||||
fi
|
||||
@@ -319,6 +319,7 @@ _rg() {
|
||||
'(-q --quiet)'{-q,--quiet}'[suppress normal output]'
|
||||
'--regex-size-limit=[specify upper size limit of compiled regex]:regex size (bytes)'
|
||||
'*'{-u,--unrestricted}'[reduce level of "smart" searching]'
|
||||
'--stop-on-nonmatch[stop on first non-matching line after a matching one]'
|
||||
|
||||
+ operand # Operands
|
||||
'(--files --type-list file regexp)1: :_guard "^-*" pattern'
|
||||
@@ -432,9 +433,13 @@ _rg_types() {
|
||||
local -a expl
|
||||
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 "$@"
|
||||
|
@@ -18,7 +18,7 @@ pub struct DecompressionMatcherBuilder {
|
||||
}
|
||||
|
||||
/// A representation of a single command for decompressing data
|
||||
/// out-of-proccess.
|
||||
/// out-of-process.
|
||||
#[derive(Clone, Debug)]
|
||||
struct DecompressionCommand {
|
||||
/// The glob that matches this command.
|
||||
@@ -132,7 +132,7 @@ impl DecompressionMatcherBuilder {
|
||||
A: AsRef<OsStr>,
|
||||
{
|
||||
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 =
|
||||
args.into_iter().map(|a| a.as_ref().to_os_string()).collect();
|
||||
self.commands.push(DecompressionCommand { glob, bin, args });
|
||||
@@ -421,6 +421,34 @@ impl io::Read for DecompressionReader {
|
||||
/// On non-Windows, this is a no-op.
|
||||
pub fn resolve_binary<P: AsRef<Path>>(
|
||||
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> {
|
||||
use std::env;
|
||||
|
||||
@@ -433,7 +461,7 @@ pub fn resolve_binary<P: AsRef<Path>>(
|
||||
}
|
||||
|
||||
let prog = prog.as_ref();
|
||||
if !cfg!(windows) || prog.is_absolute() {
|
||||
if prog.is_absolute() {
|
||||
return Ok(prog.to_path_buf());
|
||||
}
|
||||
let syspaths = match env::var_os("PATH") {
|
||||
@@ -455,9 +483,11 @@ pub fn resolve_binary<P: AsRef<Path>>(
|
||||
return Ok(abs_prog.to_path_buf());
|
||||
}
|
||||
if abs_prog.extension().is_none() {
|
||||
let abs_prog = abs_prog.with_extension("exe");
|
||||
if is_exe(&abs_prog) {
|
||||
return Ok(abs_prog.to_path_buf());
|
||||
for extension in ["com", "exe"] {
|
||||
let abs_prog = abs_prog.with_extension(extension);
|
||||
if is_exe(&abs_prog) {
|
||||
return Ok(abs_prog.to_path_buf());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -632,6 +632,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
||||
flag_sort(&mut args);
|
||||
flag_sortr(&mut args);
|
||||
flag_stats(&mut args);
|
||||
flag_stop_on_nonmatch(&mut args);
|
||||
flag_text(&mut args);
|
||||
flag_threads(&mut args);
|
||||
flag_trim(&mut args);
|
||||
@@ -698,7 +699,7 @@ fn flag_after_context(args: &mut Vec<RGArg>) {
|
||||
"\
|
||||
Show NUM lines after each match.
|
||||
|
||||
This overrides the --context and --passthru flags.
|
||||
This overrides the --passthru flag and partially overrides --context.
|
||||
"
|
||||
);
|
||||
let arg = RGArg::flag("after-context", "NUM")
|
||||
@@ -706,8 +707,7 @@ This overrides the --context and --passthru flags.
|
||||
.help(SHORT)
|
||||
.long_help(LONG)
|
||||
.number()
|
||||
.overrides("passthru")
|
||||
.overrides("context");
|
||||
.overrides("passthru");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
@@ -768,7 +768,7 @@ fn flag_before_context(args: &mut Vec<RGArg>) {
|
||||
"\
|
||||
Show NUM lines before each match.
|
||||
|
||||
This overrides the --context and --passthru flags.
|
||||
This overrides the --passthru flag and partially overrides --context.
|
||||
"
|
||||
);
|
||||
let arg = RGArg::flag("before-context", "NUM")
|
||||
@@ -776,8 +776,7 @@ This overrides the --context and --passthru flags.
|
||||
.help(SHORT)
|
||||
.long_help(LONG)
|
||||
.number()
|
||||
.overrides("passthru")
|
||||
.overrides("context");
|
||||
.overrides("passthru");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
@@ -1009,8 +1008,7 @@ fn flag_context(args: &mut Vec<RGArg>) {
|
||||
Show NUM lines before and after each match. This is equivalent to providing
|
||||
both the -B/--before-context and -A/--after-context flags with the same value.
|
||||
|
||||
This overrides both the -B/--before-context and -A/--after-context flags,
|
||||
in addition to the --passthru flag.
|
||||
This overrides the --passthru flag.
|
||||
"
|
||||
);
|
||||
let arg = RGArg::flag("context", "NUM")
|
||||
@@ -1018,9 +1016,7 @@ in addition to the --passthru flag.
|
||||
.help(SHORT)
|
||||
.long_help(LONG)
|
||||
.number()
|
||||
.overrides("passthru")
|
||||
.overrides("before-context")
|
||||
.overrides("after-context");
|
||||
.overrides("passthru");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
@@ -1711,6 +1707,8 @@ fn flag_line_number(args: &mut Vec<RGArg>) {
|
||||
"\
|
||||
Show line numbers (1-based). This is enabled by default when searching in a
|
||||
terminal.
|
||||
|
||||
This flag overrides --no-line-number.
|
||||
"
|
||||
);
|
||||
let arg = RGArg::switch("line-number")
|
||||
@@ -1725,6 +1723,8 @@ terminal.
|
||||
"\
|
||||
Suppress line numbers. This is enabled by default when not searching in a
|
||||
terminal.
|
||||
|
||||
This flag overrides --line-number.
|
||||
"
|
||||
);
|
||||
let arg = RGArg::switch("no-line-number")
|
||||
@@ -1927,13 +1927,16 @@ Nevertheless, if you only care about matches spanning at most one line, then it
|
||||
is always better to disable multiline mode.
|
||||
|
||||
This flag can be disabled with --no-multiline.
|
||||
|
||||
This overrides the --stop-on-nonmatch flag.
|
||||
"
|
||||
);
|
||||
let arg = RGArg::switch("multiline")
|
||||
.short("U")
|
||||
.help(SHORT)
|
||||
.long_help(LONG)
|
||||
.overrides("no-multiline");
|
||||
.overrides("no-multiline")
|
||||
.overrides("stop-on-nonmatch");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("no-multiline").hidden().overrides("multiline");
|
||||
@@ -2647,6 +2650,17 @@ replacement string. Capture group indices are numbered based on the position of
|
||||
the opening parenthesis of the group, where the leftmost such group is $1. The
|
||||
special $0 group corresponds to the entire match.
|
||||
|
||||
The name of a group is formed by taking the longest string of letters, numbers
|
||||
and underscores (i.e. [_0-9A-Za-z]) after the $. For example, $1a will be
|
||||
replaced with the group named '1a', not the group at index 1. If the group's
|
||||
name contains characters that aren't letters, numbers or underscores, or you
|
||||
want to immediately follow the group with another string, the name should be
|
||||
put inside braces. For example, ${1}a will take the content of the group at
|
||||
index 1 and append 'a' to the end of it.
|
||||
|
||||
If an index or name does not refer to a valid capture group, it will be
|
||||
replaced with an empty string.
|
||||
|
||||
In shells such as Bash and zsh, you should wrap the pattern in single quotes
|
||||
instead of double quotes. Otherwise, capture group indices will be replaced by
|
||||
expanded shell variables which will most likely be empty.
|
||||
@@ -2844,6 +2858,25 @@ This flag can be disabled with --no-stats.
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_stop_on_nonmatch(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Stop searching after a non-match.";
|
||||
const LONG: &str = long!(
|
||||
"\
|
||||
Enabling this option will cause ripgrep to stop reading a file once it
|
||||
encounters a non-matching line after it has encountered a matching line.
|
||||
This is useful if it is expected that all matches in a given file will be on
|
||||
sequential lines, for example due to the lines being sorted.
|
||||
|
||||
This overrides the -U/--multiline flag.
|
||||
"
|
||||
);
|
||||
let arg = RGArg::switch("stop-on-nonmatch")
|
||||
.help(SHORT)
|
||||
.long_help(LONG)
|
||||
.overrides("multiline");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_text(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Search binary files as if they were text.";
|
||||
const LONG: &str = long!(
|
||||
|
@@ -41,7 +41,7 @@ use crate::path_printer::{PathPrinter, PathPrinterBuilder};
|
||||
use crate::search::{
|
||||
PatternMatcher, Printer, SearchWorker, SearchWorkerBuilder,
|
||||
};
|
||||
use crate::subject::SubjectBuilder;
|
||||
use crate::subject::{Subject, SubjectBuilder};
|
||||
use crate::Result;
|
||||
|
||||
/// The command that ripgrep should execute based on the command line
|
||||
@@ -324,6 +324,46 @@ impl Args {
|
||||
.build())
|
||||
}
|
||||
|
||||
/// Returns true if and only if `stat`-related sorting is required
|
||||
pub fn needs_stat_sort(&self) -> bool {
|
||||
return self.matches().sort_by().map_or(
|
||||
false,
|
||||
|sort_by| match sort_by.kind {
|
||||
SortByKind::LastModified
|
||||
| SortByKind::Created
|
||||
| SortByKind::LastAccessed => sort_by.check().is_ok(),
|
||||
_ => false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Sort subjects if a sorter is specified, but only if the sort requires
|
||||
/// stat calls. Non-stat related sorts are handled during file traversal
|
||||
///
|
||||
/// This function assumes that it is known that a stat-related sort is
|
||||
/// required, and does not check for it again.
|
||||
///
|
||||
/// It is important that that precondition is fulfilled, since this function
|
||||
/// consumes the subjects iterator, and is therefore a blocking function.
|
||||
pub fn sort_by_stat<I>(&self, subjects: I) -> Vec<Subject>
|
||||
where
|
||||
I: Iterator<Item = Subject>,
|
||||
{
|
||||
let sorter = match self.matches().sort_by() {
|
||||
Ok(v) => v,
|
||||
Err(_) => return subjects.collect(),
|
||||
};
|
||||
use SortByKind::*;
|
||||
let mut keyed = match sorter.kind {
|
||||
LastModified => load_timestamps(subjects, |m| m.modified()),
|
||||
LastAccessed => load_timestamps(subjects, |m| m.accessed()),
|
||||
Created => load_timestamps(subjects, |m| m.created()),
|
||||
_ => return subjects.collect(),
|
||||
};
|
||||
keyed.sort_by(|a, b| sort_by_option(&a.0, &b.0, sorter.reverse));
|
||||
keyed.into_iter().map(|v| v.1).collect()
|
||||
}
|
||||
|
||||
/// Return a parallel walker that may use additional threads.
|
||||
pub fn walker_parallel(&self) -> Result<WalkParallel> {
|
||||
Ok(self
|
||||
@@ -404,44 +444,23 @@ impl SortBy {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_walk_builder(self, builder: &mut WalkBuilder) {
|
||||
// This isn't entirely optimal. In particular, we will wind up issuing
|
||||
// a stat for many files redundantly. Aside from having potentially
|
||||
// inconsistent results with respect to sorting, this is also slow.
|
||||
// We could fix this here at the expense of memory by caching stat
|
||||
// calls. A better fix would be to find a way to push this down into
|
||||
// directory traversal itself, but that's a somewhat nasty change.
|
||||
/// Load sorters only if they are applicable at the walk stage.
|
||||
///
|
||||
/// In particular, sorts that involve `stat` calls are not loaded because
|
||||
/// the walk inherently assumes that parent directories are aware of all its
|
||||
/// decendent properties, but `stat` does not work that way.
|
||||
fn configure_builder_sort(self, builder: &mut WalkBuilder) {
|
||||
use SortByKind::*;
|
||||
match self.kind {
|
||||
SortByKind::None => {}
|
||||
SortByKind::Path => {
|
||||
if self.reverse {
|
||||
builder.sort_by_file_name(|a, b| a.cmp(b).reverse());
|
||||
} else {
|
||||
builder.sort_by_file_name(|a, b| a.cmp(b));
|
||||
}
|
||||
Path if self.reverse => {
|
||||
builder.sort_by_file_name(|a, b| a.cmp(b).reverse());
|
||||
}
|
||||
SortByKind::LastModified => {
|
||||
builder.sort_by_file_path(move |a, b| {
|
||||
sort_by_metadata_time(a, b, self.reverse, |md| {
|
||||
md.modified()
|
||||
})
|
||||
});
|
||||
Path => {
|
||||
builder.sort_by_file_name(|a, b| a.cmp(b));
|
||||
}
|
||||
SortByKind::LastAccessed => {
|
||||
builder.sort_by_file_path(move |a, b| {
|
||||
sort_by_metadata_time(a, b, self.reverse, |md| {
|
||||
md.accessed()
|
||||
})
|
||||
});
|
||||
}
|
||||
SortByKind::Created => {
|
||||
builder.sort_by_file_path(move |a, b| {
|
||||
sort_by_metadata_time(a, b, self.reverse, |md| {
|
||||
md.created()
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
// these use `stat` calls and will be sorted in Args::sort_by_stat()
|
||||
LastModified | LastAccessed | Created | None => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,7 +840,8 @@ impl ArgMatches {
|
||||
.before_context(ctx_before)
|
||||
.after_context(ctx_after)
|
||||
.passthru(self.is_present("passthru"))
|
||||
.memory_map(self.mmap_choice(paths));
|
||||
.memory_map(self.mmap_choice(paths))
|
||||
.stop_on_nonmatch(self.is_present("stop-on-nonmatch"));
|
||||
match self.encoding()? {
|
||||
EncodingMode::Some(enc) => {
|
||||
builder.encoding(Some(enc));
|
||||
@@ -872,12 +892,10 @@ impl ArgMatches {
|
||||
.git_exclude(!self.no_ignore_vcs() && !self.no_ignore_exclude())
|
||||
.require_git(!self.is_present("no-require-git"))
|
||||
.ignore_case_insensitive(self.ignore_file_case_insensitive());
|
||||
if !self.no_ignore() {
|
||||
if !self.no_ignore() && !self.no_ignore_dot() {
|
||||
builder.add_custom_ignore_filename(".rgignore");
|
||||
}
|
||||
let sortby = self.sort_by()?;
|
||||
sortby.check()?;
|
||||
sortby.configure_walk_builder(&mut builder);
|
||||
self.sort_by()?.configure_builder_sort(&mut builder);
|
||||
Ok(builder)
|
||||
}
|
||||
}
|
||||
@@ -992,10 +1010,10 @@ impl ArgMatches {
|
||||
/// If there was a problem parsing the values from the user as an integer,
|
||||
/// then an error is returned.
|
||||
fn contexts(&self) -> Result<(usize, usize)> {
|
||||
let after = self.usize_of("after-context")?.unwrap_or(0);
|
||||
let before = self.usize_of("before-context")?.unwrap_or(0);
|
||||
let both = self.usize_of("context")?.unwrap_or(0);
|
||||
Ok(if both > 0 { (both, both) } else { (before, after) })
|
||||
let after = self.usize_of("after-context")?.unwrap_or(both);
|
||||
let before = self.usize_of("before-context")?.unwrap_or(both);
|
||||
Ok((before, after))
|
||||
}
|
||||
|
||||
/// Returns the unescaped context separator in UTF-8 bytes.
|
||||
@@ -1739,32 +1757,18 @@ fn u64_to_usize(arg_name: &str, value: Option<u64>) -> Result<Option<usize>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a comparator for sorting two files according to a system time
|
||||
/// extracted from the file's metadata.
|
||||
///
|
||||
/// If there was a problem extracting the metadata or if the time is not
|
||||
/// available, then both entries compare equal.
|
||||
fn sort_by_metadata_time<G>(
|
||||
p1: &Path,
|
||||
p2: &Path,
|
||||
/// Sorts by an optional parameter.
|
||||
//
|
||||
/// If parameter is found to be `None`, both entries compare equal.
|
||||
fn sort_by_option<T: Ord>(
|
||||
p1: &Option<T>,
|
||||
p2: &Option<T>,
|
||||
reverse: bool,
|
||||
get_time: G,
|
||||
) -> cmp::Ordering
|
||||
where
|
||||
G: Fn(&fs::Metadata) -> io::Result<SystemTime>,
|
||||
{
|
||||
let t1 = match p1.metadata().and_then(|md| get_time(&md)) {
|
||||
Ok(t) => t,
|
||||
Err(_) => return cmp::Ordering::Equal,
|
||||
};
|
||||
let t2 = match p2.metadata().and_then(|md| get_time(&md)) {
|
||||
Ok(t) => t,
|
||||
Err(_) => return cmp::Ordering::Equal,
|
||||
};
|
||||
if reverse {
|
||||
t1.cmp(&t2).reverse()
|
||||
} else {
|
||||
t1.cmp(&t2)
|
||||
) -> cmp::Ordering {
|
||||
match (p1, p2, reverse) {
|
||||
(Some(p1), Some(p2), true) => p1.cmp(&p2).reverse(),
|
||||
(Some(p1), Some(p2), false) => p1.cmp(&p2),
|
||||
_ => cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1818,3 +1822,17 @@ fn current_dir() -> Result<PathBuf> {
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Tries to assign a timestamp to every `Subject` in the vector to help with
|
||||
/// sorting Subjects by time.
|
||||
fn load_timestamps<G>(
|
||||
subjects: impl Iterator<Item = Subject>,
|
||||
get_time: G,
|
||||
) -> Vec<(Option<SystemTime>, Subject)>
|
||||
where
|
||||
G: Fn(&fs::Metadata) -> io::Result<SystemTime>,
|
||||
{
|
||||
subjects
|
||||
.map(|s| (s.path().metadata().and_then(|m| get_time(&m)).ok(), s))
|
||||
.collect()
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ impl Log for Logger {
|
||||
fn log(&self, record: &log::Record<'_>) {
|
||||
match (record.file(), record.line()) {
|
||||
(Some(file), Some(line)) => {
|
||||
eprintln!(
|
||||
eprintln_locked!(
|
||||
"{}|{}|{}:{}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
@@ -43,7 +43,7 @@ impl Log for Logger {
|
||||
);
|
||||
}
|
||||
(Some(file), None) => {
|
||||
eprintln!(
|
||||
eprintln_locked!(
|
||||
"{}|{}|{}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
@@ -52,7 +52,7 @@ impl Log for Logger {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
eprintln_locked!(
|
||||
"{}|{}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
@@ -63,6 +63,6 @@ impl Log for Logger {
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
// We use eprintln! which is flushed on every call.
|
||||
// We use eprintln_locked! which is flushed on every call.
|
||||
}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ type Result<T> = ::std::result::Result<T, Box<dyn error::Error>>;
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = Args::parse().and_then(try_main) {
|
||||
eprintln!("{}", err);
|
||||
eprintln_locked!("{}", err);
|
||||
process::exit(2);
|
||||
}
|
||||
}
|
||||
@@ -77,53 +77,70 @@ fn try_main(args: Args) -> Result<()> {
|
||||
/// steps through the file list (current directory by default) and searches
|
||||
/// each file sequentially.
|
||||
fn search(args: &Args) -> Result<bool> {
|
||||
let started_at = Instant::now();
|
||||
let quit_after_match = args.quit_after_match()?;
|
||||
let subject_builder = args.subject_builder();
|
||||
let mut stats = args.stats()?;
|
||||
let mut searcher = args.search_worker(args.stdout())?;
|
||||
let mut matched = false;
|
||||
let mut searched = false;
|
||||
/// The meat of the routine is here. This lets us call the same iteration
|
||||
/// code over each file regardless of whether we stream over the files
|
||||
/// as they're produced by the underlying directory traversal or whether
|
||||
/// they've been collected and sorted (for example) first.
|
||||
fn iter(
|
||||
args: &Args,
|
||||
subjects: impl Iterator<Item = Subject>,
|
||||
started_at: std::time::Instant,
|
||||
) -> Result<bool> {
|
||||
let quit_after_match = args.quit_after_match()?;
|
||||
let mut stats = args.stats()?;
|
||||
let mut searcher = args.search_worker(args.stdout())?;
|
||||
let mut matched = false;
|
||||
let mut searched = false;
|
||||
|
||||
for result in args.walker()? {
|
||||
let subject = match subject_builder.build_from_result(result) {
|
||||
Some(subject) => subject,
|
||||
None => continue,
|
||||
};
|
||||
searched = true;
|
||||
let search_result = match searcher.search(&subject) {
|
||||
Ok(search_result) => search_result,
|
||||
Err(err) => {
|
||||
for subject in subjects {
|
||||
searched = true;
|
||||
let search_result = match searcher.search(&subject) {
|
||||
Ok(search_result) => search_result,
|
||||
// A broken pipe means graceful termination.
|
||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
||||
break;
|
||||
Err(err) if err.kind() == io::ErrorKind::BrokenPipe => break,
|
||||
Err(err) => {
|
||||
err_message!("{}: {}", subject.path().display(), err);
|
||||
continue;
|
||||
}
|
||||
err_message!("{}: {}", subject.path().display(), err);
|
||||
continue;
|
||||
};
|
||||
matched |= search_result.has_match();
|
||||
if let Some(ref mut stats) = stats {
|
||||
*stats += search_result.stats().unwrap();
|
||||
}
|
||||
if matched && quit_after_match {
|
||||
break;
|
||||
}
|
||||
};
|
||||
matched = matched || search_result.has_match();
|
||||
if let Some(ref mut stats) = stats {
|
||||
*stats += search_result.stats().unwrap();
|
||||
}
|
||||
if matched && quit_after_match {
|
||||
break;
|
||||
if args.using_default_path() && !searched {
|
||||
eprint_nothing_searched();
|
||||
}
|
||||
if let Some(ref stats) = stats {
|
||||
let elapsed = Instant::now().duration_since(started_at);
|
||||
// We don't care if we couldn't print this successfully.
|
||||
let _ = searcher.print_stats(elapsed, stats);
|
||||
}
|
||||
Ok(matched)
|
||||
}
|
||||
if args.using_default_path() && !searched {
|
||||
eprint_nothing_searched();
|
||||
|
||||
let started_at = Instant::now();
|
||||
let subject_builder = args.subject_builder();
|
||||
let subjects = args
|
||||
.walker()?
|
||||
.filter_map(|result| subject_builder.build_from_result(result));
|
||||
if args.needs_stat_sort() {
|
||||
let subjects = args.sort_by_stat(subjects).into_iter();
|
||||
iter(args, subjects, started_at)
|
||||
} else {
|
||||
iter(args, subjects, started_at)
|
||||
}
|
||||
if let Some(ref stats) = stats {
|
||||
let elapsed = Instant::now().duration_since(started_at);
|
||||
// We don't care if we couldn't print this successfully.
|
||||
let _ = searcher.print_stats(elapsed, stats);
|
||||
}
|
||||
Ok(matched)
|
||||
}
|
||||
|
||||
/// The top-level entry point for multi-threaded search. The parallelism is
|
||||
/// itself achieved by the recursive directory traversal. All we need to do is
|
||||
/// feed it a worker for performing a search on each file.
|
||||
///
|
||||
/// 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: &Args) -> Result<bool> {
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
@@ -214,35 +231,54 @@ fn eprint_nothing_searched() {
|
||||
/// 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 path_printer = args.path_printer(args.stdout())?;
|
||||
for result in args.walker()? {
|
||||
let subject = match subject_builder.build_from_result(result) {
|
||||
Some(subject) => subject,
|
||||
None => continue,
|
||||
};
|
||||
matched = true;
|
||||
if quit_after_match {
|
||||
break;
|
||||
}
|
||||
if let Err(err) = path_printer.write_path(subject.path()) {
|
||||
// A broken pipe means graceful termination.
|
||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
||||
/// The meat of the routine is here. This lets us call the same iteration
|
||||
/// code over each file regardless of whether we stream over the files
|
||||
/// as they're produced by the underlying directory traversal or whether
|
||||
/// they've been collected and sorted (for example) first.
|
||||
fn iter(
|
||||
args: &Args,
|
||||
subjects: impl Iterator<Item = Subject>,
|
||||
) -> Result<bool> {
|
||||
let quit_after_match = args.quit_after_match()?;
|
||||
let mut matched = false;
|
||||
let mut path_printer = args.path_printer(args.stdout())?;
|
||||
|
||||
for subject in subjects {
|
||||
matched = true;
|
||||
if quit_after_match {
|
||||
break;
|
||||
}
|
||||
// Otherwise, we have some other error that's preventing us from
|
||||
// writing to stdout, so we should bubble it up.
|
||||
return Err(err.into());
|
||||
if let Err(err) = path_printer.write_path(subject.path()) {
|
||||
// A broken pipe means graceful termination.
|
||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
||||
break;
|
||||
}
|
||||
// Otherwise, we have some other error that's preventing us from
|
||||
// writing to stdout, so we should bubble it up.
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
Ok(matched)
|
||||
}
|
||||
|
||||
let subject_builder = args.subject_builder();
|
||||
let subjects = args
|
||||
.walker()?
|
||||
.filter_map(|result| subject_builder.build_from_result(result));
|
||||
if args.needs_stat_sort() {
|
||||
let subjects = args.sort_by_stat(subjects).into_iter();
|
||||
iter(args, subjects)
|
||||
} else {
|
||||
iter(args, subjects)
|
||||
}
|
||||
Ok(matched)
|
||||
}
|
||||
|
||||
/// 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 multiple threads.
|
||||
///
|
||||
/// Requesting a sorted output from ripgrep (such as with `--sort path`) will
|
||||
/// automatically disable parallelism and hence sorting is not handled here.
|
||||
fn files_parallel(args: &Args) -> Result<bool> {
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
|
@@ -4,12 +4,28 @@ static MESSAGES: AtomicBool = AtomicBool::new(false);
|
||||
static IGNORE_MESSAGES: AtomicBool = AtomicBool::new(false);
|
||||
static ERRORED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Like eprintln, but locks STDOUT to prevent interleaving lines.
|
||||
#[macro_export]
|
||||
macro_rules! eprintln_locked {
|
||||
($($tt:tt)*) => {{
|
||||
{
|
||||
// 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();
|
||||
let _handle = stdout.lock();
|
||||
eprintln!($($tt)*);
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
/// Emit a non-fatal error message, unless messages were disabled.
|
||||
#[macro_export]
|
||||
macro_rules! message {
|
||||
($($tt:tt)*) => {
|
||||
if crate::messages::messages() {
|
||||
eprintln!($($tt)*);
|
||||
eprintln_locked!($($tt)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +46,7 @@ macro_rules! err_message {
|
||||
macro_rules! ignore_message {
|
||||
($($tt:tt)*) => {
|
||||
if crate::messages::messages() && crate::messages::ignore_messages() {
|
||||
eprintln!($($tt)*);
|
||||
eprintln_locked!($($tt)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "globset"
|
||||
version = "0.4.10" #:version
|
||||
version = "0.4.11" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Cross platform single glob and glob set matching. Glob set matching is the
|
||||
|
@@ -208,6 +208,9 @@ struct GlobOptions {
|
||||
/// Whether or not to use `\` to escape special characters.
|
||||
/// e.g., when enabled, `\*` will match a literal `*`.
|
||||
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 {
|
||||
@@ -216,6 +219,7 @@ impl GlobOptions {
|
||||
case_insensitive: false,
|
||||
literal_separator: false,
|
||||
backslash_escape: !is_separator('\\'),
|
||||
empty_alternates: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -633,6 +637,16 @@ impl<'a> GlobBuilder<'a> {
|
||||
self.opts.backslash_escape = yes;
|
||||
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 {
|
||||
@@ -714,7 +728,7 @@ impl Tokens {
|
||||
for pat in patterns {
|
||||
let mut altre = String::new();
|
||||
self.tokens_to_regex(options, &pat, &mut altre);
|
||||
if !altre.is_empty() {
|
||||
if !altre.is_empty() || options.empty_alternates {
|
||||
parts.push(altre);
|
||||
}
|
||||
}
|
||||
@@ -1020,6 +1034,7 @@ mod tests {
|
||||
casei: Option<bool>,
|
||||
litsep: Option<bool>,
|
||||
bsesc: Option<bool>,
|
||||
ealtre: Option<bool>,
|
||||
}
|
||||
|
||||
macro_rules! syntax {
|
||||
@@ -1059,6 +1074,9 @@ mod tests {
|
||||
if let Some(bsesc) = $options.bsesc {
|
||||
builder.backslash_escape(bsesc);
|
||||
}
|
||||
if let Some(ealtre) = $options.ealtre {
|
||||
builder.empty_alternates(ealtre);
|
||||
}
|
||||
let pat = builder.build().unwrap();
|
||||
assert_eq!(format!("(?-u){}", $re), pat.regex());
|
||||
}
|
||||
@@ -1082,6 +1100,9 @@ mod tests {
|
||||
if let Some(bsesc) = $options.bsesc {
|
||||
builder.backslash_escape(bsesc);
|
||||
}
|
||||
if let Some(ealtre) = $options.ealtre {
|
||||
builder.empty_alternates(ealtre);
|
||||
}
|
||||
let pat = builder.build().unwrap();
|
||||
let matcher = pat.compile_matcher();
|
||||
let strategic = pat.compile_strategic_matcher();
|
||||
@@ -1110,6 +1131,9 @@ mod tests {
|
||||
if let Some(bsesc) = $options.bsesc {
|
||||
builder.backslash_escape(bsesc);
|
||||
}
|
||||
if let Some(ealtre) = $options.ealtre {
|
||||
builder.empty_alternates(ealtre);
|
||||
}
|
||||
let pat = builder.build().unwrap();
|
||||
let matcher = pat.compile_matcher();
|
||||
let strategic = pat.compile_strategic_matcher();
|
||||
@@ -1195,13 +1219,23 @@ mod tests {
|
||||
syntaxerr!(err_range2, "[z--]", ErrorKind::InvalidRange('z', '-'));
|
||||
|
||||
const CASEI: Options =
|
||||
Options { casei: Some(true), litsep: None, bsesc: None };
|
||||
Options { casei: Some(true), litsep: None, bsesc: None, ealtre: None };
|
||||
const SLASHLIT: Options =
|
||||
Options { casei: None, litsep: Some(true), bsesc: None };
|
||||
const NOBSESC: Options =
|
||||
Options { casei: None, litsep: None, bsesc: Some(false) };
|
||||
Options { casei: None, litsep: Some(true), bsesc: None, ealtre: None };
|
||||
const NOBSESC: Options = Options {
|
||||
casei: None,
|
||||
litsep: None,
|
||||
bsesc: Some(false),
|
||||
ealtre: None,
|
||||
};
|
||||
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);
|
||||
|
||||
@@ -1326,6 +1360,9 @@ mod tests {
|
||||
matches!(matchalt11, "{*.foo,*.bar,*.wat}", "test.foo");
|
||||
matches!(matchalt12, "{*.foo,*.bar,*.wat}", "test.bar");
|
||||
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);
|
||||
#[cfg(unix)]
|
||||
@@ -1425,6 +1462,9 @@ mod tests {
|
||||
if let Some(bsesc) = $options.bsesc {
|
||||
builder.backslash_escape(bsesc);
|
||||
}
|
||||
if let Some(ealtre) = $options.ealtre {
|
||||
builder.empty_alternates(ealtre);
|
||||
}
|
||||
let pat = builder.build().unwrap();
|
||||
assert_eq!($expect, pat.$which());
|
||||
}
|
||||
|
@@ -880,6 +880,29 @@ impl RequiredExtensionStrategyBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape meta-characters within the given glob pattern.
|
||||
///
|
||||
/// The escaping works by surrounding meta-characters with brackets. For
|
||||
/// example, `*` becomes `[*]`.
|
||||
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)]
|
||||
mod tests {
|
||||
use super::{GlobSet, GlobSetBuilder};
|
||||
@@ -919,4 +942,16 @@ mod tests {
|
||||
assert!(!set.is_match(""));
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,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.
|
||||
/// 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 there is no embedded `.`;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::{
|
||||
de::{Error, Visitor},
|
||||
{Deserialize, Deserializer, Serialize, Serializer},
|
||||
};
|
||||
|
||||
use crate::Glob;
|
||||
|
||||
@@ -12,19 +14,67 @@ impl Serialize for Glob {
|
||||
}
|
||||
}
|
||||
|
||||
struct GlobVisitor;
|
||||
|
||||
impl<'a> Visitor<'a> 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 {
|
||||
fn deserialize<D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Self, D::Error> {
|
||||
let glob = <&str as Deserialize>::deserialize(deserializer)?;
|
||||
Glob::new(glob).map_err(D::Error::custom)
|
||||
deserializer.deserialize_str(GlobVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::Glob;
|
||||
|
||||
#[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]
|
||||
fn glob_json_works() {
|
||||
let test_glob = Glob::new("src/**/*.rs").unwrap();
|
||||
|
@@ -23,7 +23,7 @@ globset = { version = "0.4.10", path = "../globset" }
|
||||
lazy_static = "1.1"
|
||||
log = "0.4.5"
|
||||
memchr = "2.5"
|
||||
regex = "1.8.3"
|
||||
regex = { version = "1.9.0", default-features = false, features = ["perf", "std", "unicode-gencat"] }
|
||||
same-file = "1.0.4"
|
||||
thread_local = "1"
|
||||
walkdir = "2.2.7"
|
||||
|
@@ -9,105 +9,113 @@
|
||||
/// Please try to keep this list sorted lexicographically and wrapped to 79
|
||||
/// columns (inclusive).
|
||||
#[rustfmt::skip]
|
||||
pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
||||
("agda", &["*.agda", "*.lagda"]),
|
||||
("aidl", &["*.aidl"]),
|
||||
("amake", &["*.mk", "*.bp"]),
|
||||
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
|
||||
("asm", &["*.asm", "*.s", "*.S"]),
|
||||
("asp", &[
|
||||
pub const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["ada"], &["*.adb", "*.ads"]),
|
||||
(&["agda"], &["*.agda", "*.lagda"]),
|
||||
(&["aidl"], &["*.aidl"]),
|
||||
(&["alire"], &["alire.toml"]),
|
||||
(&["amake"], &["*.mk", "*.bp"]),
|
||||
(&["asciidoc"], &["*.adoc", "*.asc", "*.asciidoc"]),
|
||||
(&["asm"], &["*.asm", "*.s", "*.S"]),
|
||||
(&["asp"], &[
|
||||
"*.aspx", "*.aspx.cs", "*.aspx.vb", "*.ascx", "*.ascx.cs",
|
||||
"*.ascx.vb", "*.asp"
|
||||
]),
|
||||
("ats", &["*.ats", "*.dats", "*.sats", "*.hats"]),
|
||||
("avro", &["*.avdl", "*.avpr", "*.avsc"]),
|
||||
("awk", &["*.awk"]),
|
||||
("bazel", &[
|
||||
(&["ats"], &["*.ats", "*.dats", "*.sats", "*.hats"]),
|
||||
(&["avro"], &["*.avdl", "*.avpr", "*.avsc"]),
|
||||
(&["awk"], &["*.awk"]),
|
||||
(&["bat", "batch"], &["*.bat"]),
|
||||
(&["bazel"], &[
|
||||
"*.bazel", "*.bzl", "*.BUILD", "*.bazelrc", "BUILD", "MODULE.bazel",
|
||||
"WORKSPACE", "WORKSPACE.bazel",
|
||||
]),
|
||||
("bitbake", &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
||||
("brotli", &["*.br"]),
|
||||
("buildstream", &["*.bst"]),
|
||||
("bzip2", &["*.bz2", "*.tbz2"]),
|
||||
("c", &["*.[chH]", "*.[chH].in", "*.cats"]),
|
||||
("cabal", &["*.cabal"]),
|
||||
("candid", &["*.did"]),
|
||||
("carp", &["*.carp"]),
|
||||
("cbor", &["*.cbor"]),
|
||||
("ceylon", &["*.ceylon"]),
|
||||
("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
|
||||
("cmake", &["*.cmake", "CMakeLists.txt"]),
|
||||
("coffeescript", &["*.coffee"]),
|
||||
("config", &["*.cfg", "*.conf", "*.config", "*.ini"]),
|
||||
("coq", &["*.v"]),
|
||||
("cpp", &[
|
||||
(&["bitbake"], &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
||||
(&["brotli"], &["*.br"]),
|
||||
(&["buildstream"], &["*.bst"]),
|
||||
(&["bzip2"], &["*.bz2", "*.tbz2"]),
|
||||
(&["c"], &["*.[chH]", "*.[chH].in", "*.cats"]),
|
||||
(&["cabal"], &["*.cabal"]),
|
||||
(&["candid"], &["*.did"]),
|
||||
(&["carp"], &["*.carp"]),
|
||||
(&["cbor"], &["*.cbor"]),
|
||||
(&["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].in", "*.cc.in", "*.[ch]pp.in", "*.[ch]xx.in", "*.hh.in",
|
||||
]),
|
||||
("creole", &["*.creole"]),
|
||||
("crystal", &["Projectfile", "*.cr", "*.ecr", "shard.yml"]),
|
||||
("cs", &["*.cs"]),
|
||||
("csharp", &["*.cs"]),
|
||||
("cshtml", &["*.cshtml"]),
|
||||
("css", &["*.css", "*.scss"]),
|
||||
("csv", &["*.csv"]),
|
||||
("cuda", &["*.cu", "*.cuh"]),
|
||||
("cython", &["*.pyx", "*.pxi", "*.pxd"]),
|
||||
("d", &["*.d"]),
|
||||
("dart", &["*.dart"]),
|
||||
("devicetree", &["*.dts", "*.dtsi"]),
|
||||
("dhall", &["*.dhall"]),
|
||||
("diff", &["*.patch", "*.diff"]),
|
||||
("docker", &["*Dockerfile*"]),
|
||||
("dockercompose", &["docker-compose.yml", "docker-compose.*.yml"]),
|
||||
("dts", &["*.dts", "*.dtsi"]),
|
||||
("dvc", &["Dvcfile", "*.dvc"]),
|
||||
("ebuild", &["*.ebuild"]),
|
||||
("edn", &["*.edn"]),
|
||||
("elisp", &["*.el"]),
|
||||
("elixir", &["*.ex", "*.eex", "*.exs"]),
|
||||
("elm", &["*.elm"]),
|
||||
("erb", &["*.erb"]),
|
||||
("erlang", &["*.erl", "*.hrl"]),
|
||||
("fennel", &["*.fnl"]),
|
||||
("fidl", &["*.fidl"]),
|
||||
("fish", &["*.fish"]),
|
||||
("flatbuffers", &["*.fbs"]),
|
||||
("fortran", &[
|
||||
(&["creole"], &["*.creole"]),
|
||||
(&["crystal"], &["Projectfile", "*.cr", "*.ecr", "shard.yml"]),
|
||||
(&["cs"], &["*.cs"]),
|
||||
(&["csharp"], &["*.cs"]),
|
||||
(&["cshtml"], &["*.cshtml"]),
|
||||
(&["css"], &["*.css", "*.scss"]),
|
||||
(&["csv"], &["*.csv"]),
|
||||
(&["cuda"], &["*.cu", "*.cuh"]),
|
||||
(&["cython"], &["*.pyx", "*.pxi", "*.pxd"]),
|
||||
(&["d"], &["*.d"]),
|
||||
(&["dart"], &["*.dart"]),
|
||||
(&["devicetree"], &["*.dts", "*.dtsi"]),
|
||||
(&["dhall"], &["*.dhall"]),
|
||||
(&["diff"], &["*.patch", "*.diff"]),
|
||||
(&["dita"], &["*.dita", "*.ditamap", "*.ditaval"]),
|
||||
(&["docker"], &["*Dockerfile*"]),
|
||||
(&["dockercompose"], &["docker-compose.yml", "docker-compose.*.yml"]),
|
||||
(&["dts"], &["*.dts", "*.dtsi"]),
|
||||
(&["dvc"], &["Dvcfile", "*.dvc"]),
|
||||
(&["ebuild"], &["*.ebuild", "*.eclass"]),
|
||||
(&["edn"], &["*.edn"]),
|
||||
(&["elisp"], &["*.el"]),
|
||||
(&["elixir"], &["*.ex", "*.eex", "*.exs", "*.heex", "*.leex", "*.livemd"]),
|
||||
(&["elm"], &["*.elm"]),
|
||||
(&["erb"], &["*.erb"]),
|
||||
(&["erlang"], &["*.erl", "*.hrl"]),
|
||||
(&["fennel"], &["*.fnl"]),
|
||||
(&["fidl"], &["*.fidl"]),
|
||||
(&["fish"], &["*.fish"]),
|
||||
(&["flatbuffers"], &["*.fbs"]),
|
||||
(&["fortran"], &[
|
||||
"*.f", "*.F", "*.f77", "*.F77", "*.pfo",
|
||||
"*.f90", "*.F90", "*.f95", "*.F95",
|
||||
]),
|
||||
("fsharp", &["*.fs", "*.fsx", "*.fsi"]),
|
||||
("fut", &["*.fut"]),
|
||||
("gap", &["*.g", "*.gap", "*.gi", "*.gd", "*.tst"]),
|
||||
("gn", &["*.gn", "*.gni"]),
|
||||
("go", &["*.go"]),
|
||||
("gradle", &["*.gradle"]),
|
||||
("groovy", &["*.groovy", "*.gradle"]),
|
||||
("gzip", &["*.gz", "*.tgz"]),
|
||||
("h", &["*.h", "*.hh", "*.hpp"]),
|
||||
("haml", &["*.haml"]),
|
||||
("hare", &["*.ha"]),
|
||||
("haskell", &["*.hs", "*.lhs", "*.cpphs", "*.c2hs", "*.hsc"]),
|
||||
("hbs", &["*.hbs"]),
|
||||
("hs", &["*.hs", "*.lhs"]),
|
||||
("html", &["*.htm", "*.html", "*.ejs"]),
|
||||
("hy", &["*.hy"]),
|
||||
("idris", &["*.idr", "*.lidr"]),
|
||||
("janet", &["*.janet"]),
|
||||
("java", &["*.java", "*.jsp", "*.jspx", "*.properties"]),
|
||||
("jinja", &["*.j2", "*.jinja", "*.jinja2"]),
|
||||
("jl", &["*.jl"]),
|
||||
("js", &["*.js", "*.jsx", "*.vue", "*.cjs", "*.mjs"]),
|
||||
("json", &["*.json", "composer.lock"]),
|
||||
("jsonl", &["*.jsonl"]),
|
||||
("julia", &["*.jl"]),
|
||||
("jupyter", &["*.ipynb", "*.jpynb"]),
|
||||
("k", &["*.k"]),
|
||||
("kotlin", &["*.kt", "*.kts"]),
|
||||
("less", &["*.less"]),
|
||||
("license", &[
|
||||
(&["fsharp"], &["*.fs", "*.fsx", "*.fsi"]),
|
||||
(&["fut"], &["*.fut"]),
|
||||
(&["gap"], &["*.g", "*.gap", "*.gi", "*.gd", "*.tst"]),
|
||||
(&["gn"], &["*.gn", "*.gni"]),
|
||||
(&["go"], &["*.go"]),
|
||||
(&["gprbuild"], &["*.gpr"]),
|
||||
(&["gradle"], &["*.gradle"]),
|
||||
(&["graphql"], &["*.graphql", "*.graphqls"]),
|
||||
(&["groovy"], &["*.groovy", "*.gradle"]),
|
||||
(&["gzip"], &["*.gz", "*.tgz"]),
|
||||
(&["h"], &["*.h", "*.hh", "*.hpp"]),
|
||||
(&["haml"], &["*.haml"]),
|
||||
(&["hare"], &["*.ha"]),
|
||||
(&["haskell"], &["*.hs", "*.lhs", "*.cpphs", "*.c2hs", "*.hsc"]),
|
||||
(&["hbs"], &["*.hbs"]),
|
||||
(&["hs"], &["*.hs", "*.lhs"]),
|
||||
(&["html"], &["*.htm", "*.html", "*.ejs"]),
|
||||
(&["hy"], &["*.hy"]),
|
||||
(&["idris"], &["*.idr", "*.lidr"]),
|
||||
(&["janet"], &["*.janet"]),
|
||||
(&["java"], &["*.java", "*.jsp", "*.jspx", "*.properties"]),
|
||||
(&["jinja"], &["*.j2", "*.jinja", "*.jinja2"]),
|
||||
(&["jl"], &["*.jl"]),
|
||||
(&["js"], &["*.js", "*.jsx", "*.vue", "*.cjs", "*.mjs"]),
|
||||
(&["json"], &["*.json", "composer.lock"]),
|
||||
(&["jsonl"], &["*.jsonl"]),
|
||||
(&["julia"], &["*.jl"]),
|
||||
(&["jupyter"], &["*.ipynb", "*.jpynb"]),
|
||||
(&["k"], &["*.k"]),
|
||||
(&["kotlin"], &["*.kt", "*.kts"]),
|
||||
(&["less"], &["*.less"]),
|
||||
(&["license"], &[
|
||||
// General
|
||||
"COPYING", "COPYING[.-]*",
|
||||
"COPYRIGHT", "COPYRIGHT[.-]*",
|
||||
@@ -134,80 +142,91 @@ pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
||||
"MPL-*[0-9]*",
|
||||
"OFL-*[0-9]*",
|
||||
]),
|
||||
("lilypond", &["*.ly", "*.ily"]),
|
||||
("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
||||
("lock", &["*.lock", "package-lock.json"]),
|
||||
("log", &["*.log"]),
|
||||
("lua", &["*.lua"]),
|
||||
("lz4", &["*.lz4"]),
|
||||
("lzma", &["*.lzma"]),
|
||||
("m4", &["*.ac", "*.m4"]),
|
||||
("make", &[
|
||||
(&["lilypond"], &["*.ly", "*.ily"]),
|
||||
(&["lisp"], &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
||||
(&["lock"], &["*.lock", "package-lock.json"]),
|
||||
(&["log"], &["*.log"]),
|
||||
(&["lua"], &["*.lua"]),
|
||||
(&["lz4"], &["*.lz4"]),
|
||||
(&["lzma"], &["*.lzma"]),
|
||||
(&["m4"], &["*.ac", "*.m4"]),
|
||||
(&["make"], &[
|
||||
"[Gg][Nn][Uu]makefile", "[Mm]akefile",
|
||||
"[Gg][Nn][Uu]makefile.am", "[Mm]akefile.am",
|
||||
"[Gg][Nn][Uu]makefile.in", "[Mm]akefile.in",
|
||||
"*.mk", "*.mak"
|
||||
]),
|
||||
("mako", &["*.mako", "*.mao"]),
|
||||
("man", &["*.[0-9lnpx]", "*.[0-9][cEFMmpSx]"]),
|
||||
("markdown", &["*.markdown", "*.md", "*.mdown", "*.mdwn", "*.mkd", "*.mkdn"]),
|
||||
("matlab", &["*.m"]),
|
||||
("md", &["*.markdown", "*.md", "*.mdown", "*.mdwn", "*.mkd", "*.mkdn"]),
|
||||
("meson", &["meson.build", "meson_options.txt"]),
|
||||
("minified", &["*.min.html", "*.min.css", "*.min.js"]),
|
||||
("mint", &["*.mint"]),
|
||||
("mk", &["mkfile"]),
|
||||
("ml", &["*.ml"]),
|
||||
("motoko", &["*.mo"]),
|
||||
("msbuild", &[
|
||||
(&["mako"], &["*.mako", "*.mao"]),
|
||||
(&["man"], &["*.[0-9lnpx]", "*.[0-9][cEFMmpSx]"]),
|
||||
(&["markdown", "md"], &[
|
||||
"*.markdown",
|
||||
"*.md",
|
||||
"*.mdown",
|
||||
"*.mdwn",
|
||||
"*.mkd",
|
||||
"*.mkdn",
|
||||
"*.mdx",
|
||||
]),
|
||||
(&["matlab"], &["*.m"]),
|
||||
(&["meson"], &["meson.build", "meson_options.txt"]),
|
||||
(&["minified"], &["*.min.html", "*.min.css", "*.min.js"]),
|
||||
(&["mint"], &["*.mint"]),
|
||||
(&["mk"], &["mkfile"]),
|
||||
(&["ml"], &["*.ml"]),
|
||||
(&["motoko"], &["*.mo"]),
|
||||
(&["msbuild"], &[
|
||||
"*.csproj", "*.fsproj", "*.vcxproj", "*.proj", "*.props", "*.targets",
|
||||
"*.sln",
|
||||
]),
|
||||
("nim", &["*.nim", "*.nimf", "*.nimble", "*.nims"]),
|
||||
("nix", &["*.nix"]),
|
||||
("objc", &["*.h", "*.m"]),
|
||||
("objcpp", &["*.h", "*.mm"]),
|
||||
("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
||||
("org", &["*.org", "*.org_archive"]),
|
||||
("pants", &["BUILD"]),
|
||||
("pascal", &["*.pas", "*.dpr", "*.lpr", "*.pp", "*.inc"]),
|
||||
("pdf", &["*.pdf"]),
|
||||
("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm", "*.t"]),
|
||||
("php", &[
|
||||
(&["nim"], &["*.nim", "*.nimf", "*.nimble", "*.nims"]),
|
||||
(&["nix"], &["*.nix"]),
|
||||
(&["objc"], &["*.h", "*.m"]),
|
||||
(&["objcpp"], &["*.h", "*.mm"]),
|
||||
(&["ocaml"], &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
||||
(&["org"], &["*.org", "*.org_archive"]),
|
||||
(&["pants"], &["BUILD"]),
|
||||
(&["pascal"], &["*.pas", "*.dpr", "*.lpr", "*.pp", "*.inc"]),
|
||||
(&["pdf"], &["*.pdf"]),
|
||||
(&["perl"], &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm", "*.t"]),
|
||||
(&["php"], &[
|
||||
// note that PHP 6 doesn't exist
|
||||
// See: https://wiki.php.net/rfc/php6
|
||||
"*.php", "*.php3", "*.php4", "*.php5", "*.php7", "*.php8",
|
||||
"*.pht", "*.phtml"
|
||||
]),
|
||||
("po", &["*.po"]),
|
||||
("pod", &["*.pod"]),
|
||||
("postscript", &["*.eps", "*.ps"]),
|
||||
("protobuf", &["*.proto"]),
|
||||
("ps", &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
|
||||
("puppet", &["*.epp", "*.erb", "*.pp", "*.rb"]),
|
||||
("purs", &["*.purs"]),
|
||||
("py", &["*.py", "*.pyi"]),
|
||||
("qmake", &["*.pro", "*.pri", "*.prf"]),
|
||||
("qml", &["*.qml"]),
|
||||
("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
|
||||
("racket", &["*.rkt"]),
|
||||
("rdoc", &["*.rdoc"]),
|
||||
("readme", &["README*", "*README"]),
|
||||
("reasonml", &["*.re", "*.rei"]),
|
||||
("red", &["*.r", "*.red", "*.reds"]),
|
||||
("rescript", &["*.res", "*.resi"]),
|
||||
("robot", &["*.robot"]),
|
||||
("rst", &["*.rst"]),
|
||||
("ruby", &[
|
||||
(&["po"], &["*.po"]),
|
||||
(&["pod"], &["*.pod"]),
|
||||
(&["postscript"], &["*.eps", "*.ps"]),
|
||||
(&["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
|
||||
"config.ru", "Gemfile", ".irbrc", "Rakefile",
|
||||
// Extensions
|
||||
"*.gemspec", "*.rb", "*.rbw"
|
||||
]),
|
||||
("rust", &["*.rs"]),
|
||||
("sass", &["*.sass", "*.scss"]),
|
||||
("scala", &["*.scala", "*.sbt"]),
|
||||
("sh", &[
|
||||
(&["rust"], &["*.rs"]),
|
||||
(&["sass"], &["*.sass", "*.scss"]),
|
||||
(&["scala"], &["*.scala", "*.sbt"]),
|
||||
(&["sh"], &[
|
||||
// Portable/misc. init files
|
||||
".login", ".logout", ".profile", "profile",
|
||||
// bash-specific init files
|
||||
@@ -230,64 +249,66 @@ pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
||||
// Extensions
|
||||
"*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh", "*.zsh",
|
||||
]),
|
||||
("slim", &["*.skim", "*.slim", "*.slime"]),
|
||||
("smarty", &["*.tpl"]),
|
||||
("sml", &["*.sml", "*.sig"]),
|
||||
("solidity", &["*.sol"]),
|
||||
("soy", &["*.soy"]),
|
||||
("spark", &["*.spark"]),
|
||||
("spec", &["*.spec"]),
|
||||
("sql", &["*.sql", "*.psql"]),
|
||||
("stylus", &["*.styl"]),
|
||||
("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
||||
("svg", &["*.svg"]),
|
||||
("swift", &["*.swift"]),
|
||||
("swig", &["*.def", "*.i"]),
|
||||
("systemd", &[
|
||||
(&["slim"], &["*.skim", "*.slim", "*.slime"]),
|
||||
(&["smarty"], &["*.tpl"]),
|
||||
(&["sml"], &["*.sml", "*.sig"]),
|
||||
(&["solidity"], &["*.sol"]),
|
||||
(&["soy"], &["*.soy"]),
|
||||
(&["spark"], &["*.spark"]),
|
||||
(&["spec"], &["*.spec"]),
|
||||
(&["sql"], &["*.sql", "*.psql"]),
|
||||
(&["stylus"], &["*.styl"]),
|
||||
(&["sv"], &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
||||
(&["svg"], &["*.svg"]),
|
||||
(&["swift"], &["*.swift"]),
|
||||
(&["swig"], &["*.def", "*.i"]),
|
||||
(&["systemd"], &[
|
||||
"*.automount", "*.conf", "*.device", "*.link", "*.mount", "*.path",
|
||||
"*.scope", "*.service", "*.slice", "*.socket", "*.swap", "*.target",
|
||||
"*.timer",
|
||||
]),
|
||||
("taskpaper", &["*.taskpaper"]),
|
||||
("tcl", &["*.tcl"]),
|
||||
("tex", &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib", "*.dtx", "*.ins"]),
|
||||
("texinfo", &["*.texi"]),
|
||||
("textile", &["*.textile"]),
|
||||
("tf", &[
|
||||
(&["taskpaper"], &["*.taskpaper"]),
|
||||
(&["tcl"], &["*.tcl"]),
|
||||
(&["tex"], &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib", "*.dtx", "*.ins"]),
|
||||
(&["texinfo"], &["*.texi"]),
|
||||
(&["textile"], &["*.textile"]),
|
||||
(&["tf"], &[
|
||||
"*.tf", "*.auto.tfvars", "terraform.tfvars", "*.tf.json",
|
||||
"*.auto.tfvars.json", "terraform.tfvars.json", "*.terraformrc",
|
||||
"terraform.rc", "*.tfrc", "*.terraform.lock.hcl",
|
||||
]),
|
||||
("thrift", &["*.thrift"]),
|
||||
("toml", &["*.toml", "Cargo.lock"]),
|
||||
("ts", &["*.ts", "*.tsx", "*.cts", "*.mts"]),
|
||||
("twig", &["*.twig"]),
|
||||
("txt", &["*.txt"]),
|
||||
("typoscript", &["*.typoscript", "*.ts"]),
|
||||
("vala", &["*.vala"]),
|
||||
("vb", &["*.vb"]),
|
||||
("vcl", &["*.vcl"]),
|
||||
("verilog", &["*.v", "*.vh", "*.sv", "*.svh"]),
|
||||
("vhdl", &["*.vhd", "*.vhdl"]),
|
||||
("vim", &[
|
||||
(&["thrift"], &["*.thrift"]),
|
||||
(&["toml"], &["*.toml", "Cargo.lock"]),
|
||||
(&["ts", "typescript"], &["*.ts", "*.tsx", "*.cts", "*.mts"]),
|
||||
(&["twig"], &["*.twig"]),
|
||||
(&["txt"], &["*.txt"]),
|
||||
(&["typoscript"], &["*.typoscript", "*.ts"]),
|
||||
(&["usd"], &["*.usd", "*.usda", "*.usdc"]),
|
||||
(&["v"], &["*.v"]),
|
||||
(&["vala"], &["*.vala"]),
|
||||
(&["vb"], &["*.vb"]),
|
||||
(&["vcl"], &["*.vcl"]),
|
||||
(&["verilog"], &["*.v", "*.vh", "*.sv", "*.svh"]),
|
||||
(&["vhdl"], &["*.vhd", "*.vhdl"]),
|
||||
(&["vim"], &[
|
||||
"*.vim", ".vimrc", ".gvimrc", "vimrc", "gvimrc", "_vimrc", "_gvimrc",
|
||||
]),
|
||||
("vimscript", &[
|
||||
(&["vimscript"], &[
|
||||
"*.vim", ".vimrc", ".gvimrc", "vimrc", "gvimrc", "_vimrc", "_gvimrc",
|
||||
]),
|
||||
("webidl", &["*.idl", "*.webidl", "*.widl"]),
|
||||
("wiki", &["*.mediawiki", "*.wiki"]),
|
||||
("xml", &[
|
||||
(&["webidl"], &["*.idl", "*.webidl", "*.widl"]),
|
||||
(&["wiki"], &["*.mediawiki", "*.wiki"]),
|
||||
(&["xml"], &[
|
||||
"*.xml", "*.xml.dist", "*.dtd", "*.xsl", "*.xslt", "*.xsd", "*.xjb",
|
||||
"*.rng", "*.sch", "*.xhtml",
|
||||
]),
|
||||
("xz", &["*.xz", "*.txz"]),
|
||||
("yacc", &["*.y"]),
|
||||
("yaml", &["*.yaml", "*.yml"]),
|
||||
("yang", &["*.yang"]),
|
||||
("z", &["*.Z"]),
|
||||
("zig", &["*.zig"]),
|
||||
("zsh", &[
|
||||
(&["xz"], &["*.xz", "*.txz"]),
|
||||
(&["yacc"], &["*.y"]),
|
||||
(&["yaml"], &["*.yaml", "*.yml"]),
|
||||
(&["yang"], &["*.yang"]),
|
||||
(&["z"], &["*.Z"]),
|
||||
(&["zig"], &["*.zig"]),
|
||||
(&["zsh"], &[
|
||||
".zshenv", "zshenv",
|
||||
".zlogin", "zlogin",
|
||||
".zlogout", "zlogout",
|
||||
@@ -295,7 +316,7 @@ pub const DEFAULT_TYPES: &[(&str, &[&str])] = &[
|
||||
".zshrc", "zshrc",
|
||||
"*.zsh",
|
||||
]),
|
||||
("zstd", &["*.zst", "*.zstd"]),
|
||||
(&["zstd"], &["*.zst", "*.zstd"]),
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -304,10 +325,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_types_are_sorted() {
|
||||
let mut names = DEFAULT_TYPES.iter().map(|(name, _exts)| name);
|
||||
|
||||
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,
|
||||
@@ -315,7 +334,6 @@ mod tests {
|
||||
name,
|
||||
previous_name
|
||||
);
|
||||
|
||||
previous_name = name;
|
||||
}
|
||||
}
|
||||
|
@@ -533,7 +533,7 @@ impl GitignoreBuilder {
|
||||
/// Return the file path of the current environment's global gitignore file.
|
||||
///
|
||||
/// 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,
|
||||
// both can be active at the same time, where $HOME/.gitconfig takes
|
||||
// precedent. So if $HOME/.gitconfig defines a `core.excludesFile`, then
|
||||
@@ -596,8 +596,13 @@ fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
|
||||
// probably works in more circumstances. I guess we would ideally have
|
||||
// a full INI parser. Yuck.
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE: Regex =
|
||||
Regex::new(r"(?im)^\s*excludesfile\s*=\s*(.+)\s*$").unwrap();
|
||||
static ref RE: Regex = Regex::new(
|
||||
r"(?xim-u)
|
||||
^[[:space:]]*excludesfile[[:space:]]*
|
||||
=
|
||||
[[:space:]]*(.+)[[:space:]]*$
|
||||
"
|
||||
).unwrap();
|
||||
};
|
||||
let caps = match RE.captures(data) {
|
||||
None => return None,
|
||||
|
@@ -488,9 +488,11 @@ impl TypesBuilder {
|
||||
/// Add a set of default file type definitions.
|
||||
pub fn add_defaults(&mut self) -> &mut TypesBuilder {
|
||||
static MSG: &'static str = "adding a default type should never fail";
|
||||
for &(name, exts) in DEFAULT_TYPES {
|
||||
for ext in exts {
|
||||
self.add(name, ext).expect(MSG);
|
||||
for &(names, exts) in DEFAULT_TYPES {
|
||||
for name in names {
|
||||
for ext in exts {
|
||||
self.add(name, ext).expect(MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
@@ -537,6 +539,8 @@ mod tests {
|
||||
"html:*.htm",
|
||||
"rust:*.rs",
|
||||
"js:*.js",
|
||||
"py:*.py",
|
||||
"python:*.py",
|
||||
"foo:*.{rs,foo}",
|
||||
"combo:include:html,rust",
|
||||
]
|
||||
@@ -551,6 +555,8 @@ mod tests {
|
||||
matched!(match7, types(), vec!["foo"], vec!["rust"], "main.foo");
|
||||
matched!(match8, types(), vec!["combo"], vec![], "index.html");
|
||||
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, matchnot2, types(), vec![], vec!["rust"], "main.rs");
|
||||
@@ -558,6 +564,8 @@ mod tests {
|
||||
matched!(not, matchnot4, types(), vec!["rust"], vec!["foo"], "main.rs");
|
||||
matched!(not, matchnot5, types(), vec!["rust"], vec!["foo"], "main.foo");
|
||||
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]
|
||||
fn test_invalid_defs() {
|
||||
@@ -569,7 +577,7 @@ mod tests {
|
||||
let original_defs = btypes.definitions();
|
||||
let bad_defs = vec![
|
||||
// Reference to type that does not exist
|
||||
"combo:include:html,python",
|
||||
"combo:include:html,qwerty",
|
||||
// Bad format
|
||||
"combo:foobar:html,rust",
|
||||
"",
|
||||
|
@@ -1681,7 +1681,7 @@ impl<'s> Worker<'s> {
|
||||
stack.pop()
|
||||
}
|
||||
|
||||
/// Signal that work has been received.
|
||||
/// Signal that work has been finished.
|
||||
fn work_done(&self) {
|
||||
self.num_pending.fetch_sub(1, Ordering::SeqCst);
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@ impl InnerLiterals {
|
||||
InnerLiterals { seq: Seq::infinite() }
|
||||
}
|
||||
|
||||
/// If it is deemed advantageuous to do so (via various suspicious
|
||||
/// If it is deemed advantageous to do so (via various suspicious
|
||||
/// heuristics), this will return a single regular expression pattern that
|
||||
/// matches a subset of the language matched by the regular expression that
|
||||
/// generated these literal sets. The idea here is that the pattern
|
||||
|
@@ -10,6 +10,12 @@ use crate::sink::{
|
||||
};
|
||||
use grep_matcher::{LineMatchKind, Matcher};
|
||||
|
||||
enum FastMatchResult {
|
||||
Continue,
|
||||
Stop,
|
||||
SwitchToSlow,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Core<'s, M: 's, S> {
|
||||
config: &'s Config,
|
||||
@@ -25,6 +31,7 @@ pub struct Core<'s, M: 's, S> {
|
||||
last_line_visited: usize,
|
||||
after_context_left: usize,
|
||||
has_sunk: bool,
|
||||
has_matched: bool,
|
||||
}
|
||||
|
||||
impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
||||
@@ -50,6 +57,7 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
||||
last_line_visited: 0,
|
||||
after_context_left: 0,
|
||||
has_sunk: false,
|
||||
has_matched: false,
|
||||
};
|
||||
if !core.searcher.multi_line_with_matcher(&core.matcher) {
|
||||
if core.is_line_by_line_fast() {
|
||||
@@ -109,7 +117,11 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
||||
|
||||
pub fn match_by_line(&mut self, buf: &[u8]) -> Result<bool, S::Error> {
|
||||
if self.is_line_by_line_fast() {
|
||||
self.match_by_line_fast(buf)
|
||||
match self.match_by_line_fast(buf)? {
|
||||
FastMatchResult::SwitchToSlow => self.match_by_line_slow(buf),
|
||||
FastMatchResult::Continue => Ok(true),
|
||||
FastMatchResult::Stop => Ok(false),
|
||||
}
|
||||
} else {
|
||||
self.match_by_line_slow(buf)
|
||||
}
|
||||
@@ -270,7 +282,9 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
||||
}
|
||||
};
|
||||
self.set_pos(line.end());
|
||||
if matched != self.config.invert_match {
|
||||
let success = matched != self.config.invert_match;
|
||||
if success {
|
||||
self.has_matched = true;
|
||||
if !self.before_context_by_line(buf, line.start())? {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -286,40 +300,51 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
if self.config.stop_on_nonmatch && !success && self.has_matched {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn match_by_line_fast(&mut self, buf: &[u8]) -> Result<bool, S::Error> {
|
||||
debug_assert!(!self.config.passthru);
|
||||
fn match_by_line_fast(
|
||||
&mut self,
|
||||
buf: &[u8],
|
||||
) -> Result<FastMatchResult, S::Error> {
|
||||
use FastMatchResult::*;
|
||||
|
||||
debug_assert!(!self.config.passthru);
|
||||
while !buf[self.pos()..].is_empty() {
|
||||
if self.config.stop_on_nonmatch && self.has_matched {
|
||||
return Ok(SwitchToSlow);
|
||||
}
|
||||
if self.config.invert_match {
|
||||
if !self.match_by_line_fast_invert(buf)? {
|
||||
return Ok(false);
|
||||
return Ok(Stop);
|
||||
}
|
||||
} else if let Some(line) = self.find_by_line_fast(buf)? {
|
||||
self.has_matched = true;
|
||||
if self.config.max_context() > 0 {
|
||||
if !self.after_context_by_line(buf, line.start())? {
|
||||
return Ok(false);
|
||||
return Ok(Stop);
|
||||
}
|
||||
if !self.before_context_by_line(buf, line.start())? {
|
||||
return Ok(false);
|
||||
return Ok(Stop);
|
||||
}
|
||||
}
|
||||
self.set_pos(line.end());
|
||||
if !self.sink_matched(buf, &line)? {
|
||||
return Ok(false);
|
||||
return Ok(Stop);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !self.after_context_by_line(buf, buf.len())? {
|
||||
return Ok(false);
|
||||
return Ok(Stop);
|
||||
}
|
||||
self.set_pos(buf.len());
|
||||
Ok(true)
|
||||
Ok(Continue)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -344,6 +369,7 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
||||
if invert_match.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
self.has_matched = true;
|
||||
if !self.after_context_by_line(buf, invert_match.start())? {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -577,6 +603,9 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
||||
if self.config.passthru {
|
||||
return false;
|
||||
}
|
||||
if self.config.stop_on_nonmatch && self.has_matched {
|
||||
return false;
|
||||
}
|
||||
if let Some(line_term) = self.matcher.line_terminator() {
|
||||
if line_term == self.config.line_term {
|
||||
return true;
|
||||
|
@@ -173,6 +173,9 @@ pub struct Config {
|
||||
encoding: Option<Encoding>,
|
||||
/// Whether to do automatic transcoding based on a BOM or not.
|
||||
bom_sniffing: bool,
|
||||
/// Whether to stop searching when a non-matching line is found after a
|
||||
/// matching line.
|
||||
stop_on_nonmatch: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -190,6 +193,7 @@ impl Default for Config {
|
||||
multi_line: false,
|
||||
encoding: None,
|
||||
bom_sniffing: true,
|
||||
stop_on_nonmatch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,6 +559,19 @@ impl SearcherBuilder {
|
||||
self.config.bom_sniffing = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Stop searching a file when a non-matching line is found after a
|
||||
/// matching line.
|
||||
///
|
||||
/// This is useful for searching sorted files where it is expected that all
|
||||
/// the matches will be on adjacent lines.
|
||||
pub fn stop_on_nonmatch(
|
||||
&mut self,
|
||||
stop_on_nonmatch: bool,
|
||||
) -> &mut SearcherBuilder {
|
||||
self.config.stop_on_nonmatch = stop_on_nonmatch;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A searcher executes searches over a haystack and writes results to a caller
|
||||
@@ -838,6 +855,13 @@ impl Searcher {
|
||||
self.config.multi_line
|
||||
}
|
||||
|
||||
/// Returns true if and only if this searcher is configured to stop when in
|
||||
/// finds a non-matching line after a matching one.
|
||||
#[inline]
|
||||
pub fn stop_on_nonmatch(&self) -> bool {
|
||||
self.config.stop_on_nonmatch
|
||||
}
|
||||
|
||||
/// Returns true if and only if this searcher will choose a multi-line
|
||||
/// strategy given the provided matcher.
|
||||
///
|
||||
|
@@ -232,6 +232,16 @@ would behave identically to the following command
|
||||
|
||||
rg --glob '!.git' foo
|
||||
|
||||
The bottom line is that every shell argument needs to be on its own line. So
|
||||
for example, a config file containing
|
||||
|
||||
-j 4
|
||||
|
||||
is probably not doing what you intend. Instead, you want
|
||||
|
||||
-j
|
||||
4
|
||||
|
||||
ripgrep also provides a flag, *--no-config*, that when present will suppress
|
||||
any and all support for configuration. This includes any future support
|
||||
for auto-loading configuration files from pre-determined paths.
|
||||
|
28
pkg/windows/Manifest.xml
Normal file
28
pkg/windows/Manifest.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!--
|
||||
This is a Windows application manifest file.
|
||||
See: https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
|
||||
-->
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- Versions rustc supports as compiler hosts -->
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 7 --><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!-- Windows 8 --><supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 8.1 --><supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 10 and 11 --><supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<!-- Use UTF-8 code page -->
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
|
||||
<activeCodePage>UTF-8</activeCodePage>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<!-- Remove (most) legacy path limits -->
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
<ws2:longPathAware>true</ws2:longPathAware>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
15
pkg/windows/README.md
Normal file
15
pkg/windows/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
This directory contains a Windows manifest for various Windows-specific
|
||||
settings.
|
||||
|
||||
The main thing we enable here is [`longPathAware`], which permits paths of the
|
||||
form `C:\` to be longer than 260 characters.
|
||||
|
||||
The approach taken here was modeled off of a [similar change for `rustc`][rustc pr].
|
||||
In particular, this manifest gets linked into the final binary. Those linker
|
||||
arguments are applied in `build.rs`.
|
||||
|
||||
This currently only applies to MSVC builds. If there's an easy way to make this
|
||||
apply to GNU builds as well, then patches are welcome.
|
||||
|
||||
[`longPathAware`]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#longpathaware
|
||||
[rustc pr]: https://github.com/rust-lang/rust/pull/96737
|
@@ -787,6 +787,28 @@ rgtest!(f1466_no_ignore_files, |dir: Dir, mut cmd: TestCommand| {
|
||||
eqnice!("foo\n", cmd.arg("-u").stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/pull/2361
|
||||
rgtest!(f2361_sort_nested_files, |dir: Dir, mut cmd: TestCommand| {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
dir.create("foo", "1");
|
||||
sleep(Duration::from_millis(100));
|
||||
dir.create_dir("dir");
|
||||
sleep(Duration::from_millis(100));
|
||||
dir.create(dir.path().join("dir").join("bar"), "1");
|
||||
|
||||
cmd.arg("--sort").arg("accessed").arg("--files");
|
||||
eqnice!("foo\ndir/bar\n", cmd.stdout());
|
||||
|
||||
dir.create("foo", "2");
|
||||
sleep(Duration::from_millis(100));
|
||||
dir.create(dir.path().join("dir").join("bar"), "2");
|
||||
sleep(Duration::from_millis(100));
|
||||
|
||||
cmd.arg("--sort").arg("accessed").arg("--files");
|
||||
eqnice!("foo\ndir/bar\n", cmd.stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1404
|
||||
rgtest!(f1404_nothing_searched_warning, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create(".ignore", "ignored-dir/**");
|
||||
@@ -921,6 +943,23 @@ rgtest!(f1842_field_match_separator, |dir: Dir, _: TestCommand| {
|
||||
eqnice!(expected, dir.command().args(&args).stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2288
|
||||
rgtest!(f2288_context_partial_override, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("test", "1\n2\n3\n4\n5\n6\n7\n8\n9\n");
|
||||
cmd.args(&["-C1", "-A2", "5", "test"]);
|
||||
eqnice!("4\n5\n6\n7\n", cmd.stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2288
|
||||
rgtest!(
|
||||
f2288_context_partial_override_rev,
|
||||
|dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("test", "1\n2\n3\n4\n5\n6\n7\n8\n9\n");
|
||||
cmd.args(&["-A2", "-C1", "5", "test"]);
|
||||
eqnice!("4\n5\n6\n7\n", cmd.stdout());
|
||||
}
|
||||
);
|
||||
|
||||
rgtest!(no_context_sep, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("test", "foo\nctx\nbar\nctx\nfoo\nctx");
|
||||
cmd.args(&["-A1", "--no-context-separator", "foo", "test"]);
|
||||
@@ -975,3 +1014,10 @@ rgtest!(no_unicode, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("test", "δ");
|
||||
cmd.arg("-i").arg("--no-unicode").arg("Δ").assert_err();
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1790
|
||||
rgtest!(stop_on_nonmatch, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("test", "line1\nline2\nline3\nline4\nline5");
|
||||
cmd.args(&["--stop-on-nonmatch", "[235]"]);
|
||||
eqnice!("test:line2\ntest:line3\n", cmd.stdout());
|
||||
});
|
||||
|
@@ -1065,3 +1065,48 @@ rgtest!(type_list, |_: Dir, mut cmd: TestCommand| {
|
||||
// This can change over time, so just make sure we print something.
|
||||
assert!(!cmd.stdout().is_empty());
|
||||
});
|
||||
|
||||
// The following series of tests seeks to test all permutations of ripgrep's
|
||||
// sorted queries.
|
||||
//
|
||||
// They all rely on this setup function, which sets up this particular file
|
||||
// structure with a particular creation order:
|
||||
// ├── a # 1
|
||||
// ├── b # 4
|
||||
// └── dir # 2
|
||||
// ├── c # 3
|
||||
// └── d # 5
|
||||
//
|
||||
// This order is important when sorting them by system time-stamps.
|
||||
fn sort_setup(dir: Dir) {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
let sub_dir = dir.path().join("dir");
|
||||
dir.create("a", "test");
|
||||
sleep(Duration::from_millis(100));
|
||||
dir.create_dir(&sub_dir);
|
||||
sleep(Duration::from_millis(100));
|
||||
dir.create(sub_dir.join("c"), "test");
|
||||
sleep(Duration::from_millis(100));
|
||||
dir.create("b", "test");
|
||||
sleep(Duration::from_millis(100));
|
||||
dir.create(sub_dir.join("d"), "test");
|
||||
}
|
||||
|
||||
rgtest!(sort_files, |dir: Dir, mut cmd: TestCommand| {
|
||||
sort_setup(dir);
|
||||
let expected = "a:test\nb:test\ndir/c:test\ndir/d:test\n";
|
||||
eqnice!(expected, cmd.args(["--sort", "path", "test"]).stdout());
|
||||
});
|
||||
|
||||
rgtest!(sort_accessed, |dir: Dir, mut cmd: TestCommand| {
|
||||
sort_setup(dir);
|
||||
let expected = "a:test\ndir/c:test\nb:test\ndir/d:test\n";
|
||||
eqnice!(expected, cmd.args(["--sort", "accessed", "test"]).stdout());
|
||||
});
|
||||
|
||||
rgtest!(sortr_accessed, |dir: Dir, mut cmd: TestCommand| {
|
||||
sort_setup(dir);
|
||||
let expected = "dir/d:test\nb:test\ndir/c:test\na:test\n";
|
||||
eqnice!(expected, cmd.args(["--sortr", "accessed", "test"]).stdout());
|
||||
});
|
||||
|
@@ -1090,6 +1090,19 @@ b=one
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2198
|
||||
rgtest!(r2198, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create(".ignore", "a");
|
||||
dir.create(".rgignore", "b");
|
||||
dir.create("a", "");
|
||||
dir.create("b", "");
|
||||
dir.create("c", "");
|
||||
|
||||
cmd.arg("--files").arg("--sort").arg("path");
|
||||
eqnice!("c\n", cmd.stdout());
|
||||
eqnice!("a\nb\nc\n", cmd.arg("--no-ignore-dot").stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2208
|
||||
rgtest!(r2208, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("test", "# Compile requirements.txt files from all found or specified requirements.in files (compile).
|
||||
@@ -1126,3 +1139,37 @@ rgtest!(r2236, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("foo/bar", "test\n");
|
||||
cmd.args(&["test"]).assert_err();
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2480
|
||||
rgtest!(r2480, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("file", "FooBar\n");
|
||||
|
||||
// no regression in empty pattern behavior
|
||||
cmd.args(&["-e", "", "file"]);
|
||||
eqnice!("FooBar\n", cmd.stdout());
|
||||
|
||||
// no regression in single pattern behavior
|
||||
let mut cmd = dir.command();
|
||||
cmd.args(&["-e", ")(", "file"]);
|
||||
eqnice!("FooBar\n", cmd.stdout());
|
||||
|
||||
// no regression in multiple patterns behavior
|
||||
let mut cmd = dir.command();
|
||||
cmd.args(&["--only-matching", "-e", "Foo", "-e", "Bar", "file"]);
|
||||
eqnice!("Foo\nBar\n", cmd.stdout());
|
||||
|
||||
// no regression in capture groups behavior
|
||||
let mut cmd = dir.command();
|
||||
cmd.args(&["-e", "Fo(oB)a(r)", "--replace", "${0}_${1}_${2}${3}", "file"]);
|
||||
eqnice!("FooBar_oB_r\n", cmd.stdout()); // note: ${3} expected to be empty
|
||||
|
||||
// flag does not leak into next pattern on match
|
||||
let mut cmd = dir.command();
|
||||
cmd.args(&["--only-matching", "-e", "(?i)foo", "-e", "bar", "file"]);
|
||||
eqnice!("Foo\n", cmd.stdout());
|
||||
|
||||
// flag does not leak into next pattern on mismatch
|
||||
let mut cmd = dir.command();
|
||||
cmd.args(&["--only-matching", "-e", "(?i)notfoo", "-e", "bar", "file"]);
|
||||
cmd.assert_err();
|
||||
});
|
||||
|
Reference in New Issue
Block a user