2019-06-17 00:37:51 +02:00
|
|
|
use std::error;
|
2018-08-30 02:53:52 +02:00
|
|
|
use std::io::{self, Write};
|
2016-02-27 18:07:26 +02:00
|
|
|
use std::process;
|
2019-11-21 16:03:27 +02:00
|
|
|
use std::sync::Mutex;
|
2018-08-03 23:26:22 +02:00
|
|
|
use std::time::Instant;
|
|
|
|
|
|
|
|
use ignore::WalkState;
|
2016-02-27 18:07:26 +02:00
|
|
|
|
2016-09-05 06:52:23 +02:00
|
|
|
use args::Args;
|
2018-08-03 23:26:22 +02:00
|
|
|
use subject::Subject;
|
2016-08-27 07:01:06 +02:00
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
#[macro_use]
|
|
|
|
mod messages;
|
2016-02-27 18:07:26 +02:00
|
|
|
|
Switch from Docopt to Clap.
There were two important reasons for the switch:
1. Performance. Docopt does poorly when the argv becomes large, which is
a reasonable common use case for search tools. (e.g., use with xargs)
2. Better failure modes. Clap knows a lot more about how a particular
argv might be invalid, and can therefore provide much clearer error
messages.
While both were important, (1) made it urgent.
Note that since Clap requires at least Rust 1.11, this will in turn
increase the minimum Rust version supported by ripgrep from Rust 1.9 to
Rust 1.11. It is therefore a breaking change, so the soonest release of
ripgrep with Clap will have to be 0.3.
There is also at least one subtle breaking change in real usage.
Previous to this commit, this used to work:
rg -e -foo
Where this would cause ripgrep to search for the string `-foo`. Clap
currently has problems supporting this use case
(see: https://github.com/kbknapp/clap-rs/issues/742),
but it can be worked around by using this instead:
rg -e [-]foo
or even
rg [-]foo
and this still works:
rg -- -foo
This commit also adds Bash, Fish and PowerShell completion files to the
release, fixes a bug that prevented ripgrep from working on file
paths containing invalid UTF-8 and shows short descriptions in the
output of `-h` but longer descriptions in the output of `--help`.
Fixes #136, Fixes #189, Fixes #210, Fixes #230
2016-11-13 04:48:11 +02:00
|
|
|
mod app;
|
2016-09-05 06:52:23 +02:00
|
|
|
mod args;
|
2018-02-04 03:33:52 +02:00
|
|
|
mod config;
|
2018-02-04 04:31:28 +02:00
|
|
|
mod logger;
|
2018-08-03 23:26:22 +02:00
|
|
|
mod path_printer;
|
|
|
|
mod search;
|
|
|
|
mod subject;
|
2016-03-11 03:48:44 +02:00
|
|
|
|
2019-04-24 23:21:38 +02:00
|
|
|
// Since Rust no longer uses jemalloc by default, ripgrep will, by default,
|
|
|
|
// use the system allocator. On Linux, this would normally be glibc's
|
|
|
|
// allocator, which is pretty good. In particular, ripgrep does not have a
|
|
|
|
// particularly allocation heavy workload, so there really isn't much
|
|
|
|
// difference (for ripgrep's purposes) between glibc's allocator and jemalloc.
|
|
|
|
//
|
|
|
|
// However, when ripgrep is built with musl, this means ripgrep will use musl's
|
|
|
|
// allocator, which appears to be substantially worse. (musl's goal is not to
|
|
|
|
// have the fastest version of everything. Its goal is to be small and amenable
|
|
|
|
// to static compilation.) Even though ripgrep isn't particularly allocation
|
|
|
|
// heavy, musl's allocator appears to slow down ripgrep quite a bit. Therefore,
|
|
|
|
// when building with musl, we use jemalloc.
|
|
|
|
//
|
|
|
|
// We don't unconditionally use jemalloc because it can be nice to use the
|
|
|
|
// system's default allocator by default. Moreover, jemalloc seems to increase
|
|
|
|
// compilation times by a bit.
|
2019-04-25 16:49:59 +02:00
|
|
|
//
|
|
|
|
// Moreover, we only do this on 64-bit systems since jemalloc doesn't support
|
|
|
|
// i686.
|
|
|
|
#[cfg(all(target_env = "musl", target_pointer_width = "64"))]
|
2019-04-24 23:21:38 +02:00
|
|
|
#[global_allocator]
|
|
|
|
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
|
|
|
|
2019-06-17 00:37:51 +02:00
|
|
|
type Result<T> = ::std::result::Result<T, Box<dyn error::Error>>;
|
2016-08-28 07:37:12 +02:00
|
|
|
|
2018-08-20 23:34:40 +02:00
|
|
|
fn main() {
|
2019-01-26 22:42:55 +02:00
|
|
|
if let Err(err) = Args::parse().and_then(try_main) {
|
|
|
|
eprintln!("{}", err);
|
|
|
|
process::exit(2);
|
2016-02-27 18:07:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-26 22:42:55 +02:00
|
|
|
fn try_main(args: Args) -> Result<()> {
|
2018-08-03 23:26:22 +02:00
|
|
|
use args::Command::*;
|
|
|
|
|
2019-11-21 16:03:27 +02:00
|
|
|
let matched = match args.command()? {
|
|
|
|
Search => search(&args),
|
|
|
|
SearchParallel => search_parallel(&args),
|
|
|
|
SearchNever => Ok(false),
|
|
|
|
Files => files(&args),
|
|
|
|
FilesParallel => files_parallel(&args),
|
|
|
|
Types => types(&args),
|
|
|
|
PCRE2Version => pcre2_version(&args),
|
|
|
|
}?;
|
2019-01-26 22:42:55 +02:00
|
|
|
if matched && (args.quiet() || !messages::errored()) {
|
|
|
|
process::exit(0)
|
|
|
|
} else if messages::errored() {
|
|
|
|
process::exit(2)
|
|
|
|
} else {
|
|
|
|
process::exit(1)
|
2016-11-06 20:09:53 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The top-level entry point for single-threaded search. This recursively
|
|
|
|
/// steps through the file list (current directory by default) and searches
|
|
|
|
/// each file sequentially.
|
2019-01-26 22:42:55 +02:00
|
|
|
fn search(args: &Args) -> Result<bool> {
|
2018-08-03 23:26:22 +02:00
|
|
|
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;
|
|
|
|
|
|
|
|
for result in args.walker()? {
|
|
|
|
let subject = match subject_builder.build_from_result(result) {
|
|
|
|
Some(subject) => subject,
|
|
|
|
None => continue,
|
|
|
|
};
|
|
|
|
let search_result = match searcher.search(&subject) {
|
|
|
|
Ok(search_result) => search_result,
|
|
|
|
Err(err) => {
|
|
|
|
// A broken pipe means graceful termination.
|
|
|
|
if err.kind() == io::ErrorKind::BrokenPipe {
|
|
|
|
break;
|
|
|
|
}
|
2019-01-26 22:42:55 +02:00
|
|
|
err_message!("{}: {}", subject.path().display(), err);
|
2018-08-03 23:26:22 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
matched = matched || search_result.has_match();
|
|
|
|
if let Some(ref mut stats) = stats {
|
|
|
|
*stats += search_result.stats().unwrap();
|
|
|
|
}
|
|
|
|
if matched && quit_after_match {
|
|
|
|
break;
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
2016-09-26 03:27:17 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
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)
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
/// 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.
|
2019-01-26 22:42:55 +02:00
|
|
|
fn search_parallel(args: &Args) -> Result<bool> {
|
2018-08-03 23:26:22 +02:00
|
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
use std::sync::atomic::Ordering::SeqCst;
|
2016-11-06 03:44:15 +02:00
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
let quit_after_match = args.quit_after_match()?;
|
|
|
|
let started_at = Instant::now();
|
2019-11-21 16:03:27 +02:00
|
|
|
let subject_builder = args.subject_builder();
|
|
|
|
let bufwtr = args.buffer_writer()?;
|
|
|
|
let stats = args.stats()?.map(Mutex::new);
|
|
|
|
let matched = AtomicBool::new(false);
|
2018-08-03 23:26:22 +02:00
|
|
|
let mut searcher_err = None;
|
|
|
|
args.walker_parallel()?.run(|| {
|
2019-11-21 16:03:27 +02:00
|
|
|
let bufwtr = &bufwtr;
|
|
|
|
let stats = &stats;
|
|
|
|
let matched = &matched;
|
|
|
|
let subject_builder = &subject_builder;
|
2018-08-03 23:26:22 +02:00
|
|
|
let mut searcher = match args.search_worker(bufwtr.buffer()) {
|
|
|
|
Ok(searcher) => searcher,
|
|
|
|
Err(err) => {
|
|
|
|
searcher_err = Some(err);
|
2019-11-21 16:03:27 +02:00
|
|
|
return Box::new(move |_| WalkState::Quit);
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Box::new(move |result| {
|
|
|
|
let subject = match subject_builder.build_from_result(result) {
|
|
|
|
Some(subject) => subject,
|
|
|
|
None => return WalkState::Continue,
|
2016-08-29 02:18:34 +02:00
|
|
|
};
|
2018-08-03 23:26:22 +02:00
|
|
|
searcher.printer().get_mut().clear();
|
|
|
|
let search_result = match searcher.search(&subject) {
|
|
|
|
Ok(search_result) => search_result,
|
|
|
|
Err(err) => {
|
2019-01-26 22:42:55 +02:00
|
|
|
err_message!("{}: {}", subject.path().display(), err);
|
2018-08-03 23:26:22 +02:00
|
|
|
return WalkState::Continue;
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
};
|
|
|
|
if search_result.has_match() {
|
|
|
|
matched.store(true, SeqCst);
|
|
|
|
}
|
|
|
|
if let Some(ref locked_stats) = *stats {
|
|
|
|
let mut stats = locked_stats.lock().unwrap();
|
|
|
|
*stats += search_result.stats().unwrap();
|
|
|
|
}
|
|
|
|
if let Err(err) = bufwtr.print(searcher.printer().get_mut()) {
|
|
|
|
// A broken pipe means graceful termination.
|
|
|
|
if err.kind() == io::ErrorKind::BrokenPipe {
|
|
|
|
return WalkState::Quit;
|
2018-02-12 19:17:22 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
// Otherwise, we continue on our merry way.
|
2019-01-26 22:42:55 +02:00
|
|
|
err_message!("{}: {}", subject.path().display(), err);
|
2018-08-03 23:26:22 +02:00
|
|
|
}
|
|
|
|
if matched.load(SeqCst) && quit_after_match {
|
|
|
|
WalkState::Quit
|
|
|
|
} else {
|
|
|
|
WalkState::Continue
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
2018-08-03 23:26:22 +02:00
|
|
|
if let Some(err) = searcher_err.take() {
|
|
|
|
return Err(err);
|
2016-08-29 02:18:34 +02:00
|
|
|
}
|
2019-11-21 16:03:27 +02:00
|
|
|
if let Some(ref locked_stats) = stats {
|
2018-08-03 23:26:22 +02:00
|
|
|
let elapsed = Instant::now().duration_since(started_at);
|
|
|
|
let stats = locked_stats.lock().unwrap();
|
|
|
|
let mut searcher = args.search_worker(args.stdout())?;
|
|
|
|
// We don't care if we couldn't print this successfully.
|
|
|
|
let _ = searcher.print_stats(elapsed, &stats);
|
2018-02-12 19:17:22 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(matched.load(SeqCst))
|
2016-08-29 02:18:34 +02:00
|
|
|
}
|
2016-08-26 03:44:37 +02:00
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
/// 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.
|
2019-01-26 22:42:55 +02:00
|
|
|
fn files(args: &Args) -> Result<bool> {
|
2018-08-03 23:26:22 +02:00
|
|
|
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,
|
2016-11-06 03:44:15 +02:00
|
|
|
None => continue,
|
|
|
|
};
|
2018-08-03 23:26:22 +02:00
|
|
|
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 {
|
2016-10-12 01:57:09 +02:00
|
|
|
break;
|
2016-09-26 03:27:17 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
// Otherwise, we have some other error that's preventing us from
|
|
|
|
// writing to stdout, so we should bubble it up.
|
|
|
|
return Err(err.into());
|
2016-11-06 21:36:08 +02:00
|
|
|
}
|
2016-09-26 03:27:17 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(matched)
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
/// 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.
|
2019-01-26 22:42:55 +02:00
|
|
|
fn files_parallel(args: &Args) -> Result<bool> {
|
2018-08-03 23:26:22 +02:00
|
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
use std::sync::atomic::Ordering::SeqCst;
|
|
|
|
use std::sync::mpsc;
|
|
|
|
use std::thread;
|
|
|
|
|
|
|
|
let quit_after_match = args.quit_after_match()?;
|
2019-11-21 16:03:27 +02:00
|
|
|
let subject_builder = args.subject_builder();
|
2018-08-03 23:26:22 +02:00
|
|
|
let mut path_printer = args.path_printer(args.stdout())?;
|
2019-11-21 16:03:27 +02:00
|
|
|
let matched = AtomicBool::new(false);
|
2018-08-03 23:26:22 +02:00
|
|
|
let (tx, rx) = mpsc::channel::<Subject>();
|
|
|
|
|
|
|
|
let print_thread = thread::spawn(move || -> io::Result<()> {
|
|
|
|
for subject in rx.iter() {
|
|
|
|
path_printer.write_path(subject.path())?;
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(())
|
2016-11-06 03:44:15 +02:00
|
|
|
});
|
2018-08-03 23:26:22 +02:00
|
|
|
args.walker_parallel()?.run(|| {
|
2019-11-21 16:03:27 +02:00
|
|
|
let subject_builder = &subject_builder;
|
|
|
|
let matched = &matched;
|
2016-11-06 03:44:15 +02:00
|
|
|
let tx = tx.clone();
|
2018-08-03 23:26:22 +02:00
|
|
|
|
2016-11-06 03:44:15 +02:00
|
|
|
Box::new(move |result| {
|
2018-08-03 23:26:22 +02:00
|
|
|
let subject = match subject_builder.build_from_result(result) {
|
|
|
|
Some(subject) => subject,
|
|
|
|
None => return WalkState::Continue,
|
|
|
|
};
|
|
|
|
matched.store(true, SeqCst);
|
|
|
|
if quit_after_match {
|
|
|
|
WalkState::Quit
|
|
|
|
} else {
|
|
|
|
match tx.send(subject) {
|
|
|
|
Ok(_) => WalkState::Continue,
|
|
|
|
Err(_) => WalkState::Quit,
|
2018-07-22 17:05:24 +02:00
|
|
|
}
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
2018-08-03 23:26:22 +02:00
|
|
|
drop(tx);
|
|
|
|
if let Err(err) = print_thread.join().unwrap() {
|
|
|
|
// A broken pipe means graceful termination, so fall through.
|
|
|
|
// Otherwise, something bad happened while writing to stdout, so bubble
|
|
|
|
// it up.
|
|
|
|
if err.kind() != io::ErrorKind::BrokenPipe {
|
|
|
|
return Err(err.into());
|
2017-05-19 14:12:38 +02:00
|
|
|
}
|
2016-09-04 03:48:23 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(matched.load(SeqCst))
|
2018-02-02 04:11:02 +02:00
|
|
|
}
|
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
/// The top-level entry point for --type-list.
|
2019-01-26 22:42:55 +02:00
|
|
|
fn types(args: &Args) -> Result<bool> {
|
2018-08-03 23:26:22 +02:00
|
|
|
let mut count = 0;
|
|
|
|
let mut stdout = args.stdout();
|
|
|
|
for def in args.type_defs()? {
|
|
|
|
count += 1;
|
|
|
|
stdout.write_all(def.name().as_bytes())?;
|
|
|
|
stdout.write_all(b": ")?;
|
2018-02-02 04:11:02 +02:00
|
|
|
|
2018-08-03 23:26:22 +02:00
|
|
|
let mut first = true;
|
|
|
|
for glob in def.globs() {
|
|
|
|
if !first {
|
|
|
|
stdout.write_all(b", ")?;
|
2016-11-06 03:44:15 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
stdout.write_all(glob.as_bytes())?;
|
|
|
|
first = false;
|
2016-09-05 16:15:13 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
stdout.write_all(b"\n")?;
|
2016-09-05 06:52:23 +02:00
|
|
|
}
|
2018-08-03 23:26:22 +02:00
|
|
|
Ok(count > 0)
|
2017-08-27 20:32:20 +02:00
|
|
|
}
|
2019-04-14 22:46:02 +02:00
|
|
|
|
|
|
|
/// The top-level entry point for --pcre2-version.
|
|
|
|
fn pcre2_version(args: &Args) -> Result<bool> {
|
|
|
|
#[cfg(feature = "pcre2")]
|
|
|
|
fn imp(args: &Args) -> Result<bool> {
|
|
|
|
use grep::pcre2;
|
|
|
|
|
|
|
|
let mut stdout = args.stdout();
|
|
|
|
|
|
|
|
let (major, minor) = pcre2::version();
|
|
|
|
writeln!(stdout, "PCRE2 {}.{} is available", major, minor)?;
|
|
|
|
|
|
|
|
if cfg!(target_pointer_width = "64") && pcre2::is_jit_available() {
|
|
|
|
writeln!(stdout, "JIT is available")?;
|
|
|
|
}
|
|
|
|
Ok(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "pcre2"))]
|
|
|
|
fn imp(args: &Args) -> Result<bool> {
|
|
|
|
let mut stdout = args.stdout();
|
|
|
|
writeln!(stdout, "PCRE2 is not available in this build of ripgrep.")?;
|
|
|
|
Ok(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
imp(args)
|
|
|
|
}
|