mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-05-19 09:40:22 -07:00
ripgrep: migrate to libripgrep
This commit is contained in:
parent
b3769ef8f1
commit
7eb34e8b32
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -194,7 +194,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grep2"
|
||||
version = "0.1.8"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"grep-matcher 0.0.1",
|
||||
"grep-printer 0.0.1",
|
||||
@ -345,6 +345,7 @@ dependencies = [
|
||||
"encoding_rs_io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.1",
|
||||
"grep 0.1.9",
|
||||
"grep2 0.2.0",
|
||||
"ignore 0.4.3",
|
||||
"lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -45,6 +45,7 @@ encoding_rs = "0.8"
|
||||
encoding_rs_io = "0.1"
|
||||
globset = { version = "0.4.0", path = "globset" }
|
||||
grep = { version = "0.1.8", path = "grep" }
|
||||
grep2 = { version = "0.2.0", path = "grep2" }
|
||||
ignore = { version = "0.4.0", path = "ignore" }
|
||||
lazy_static = "1"
|
||||
libc = "0.2"
|
||||
|
@ -131,6 +131,10 @@ _rg() {
|
||||
'--mmap[search using memory maps when possible]'
|
||||
"--no-mmap[don't search using memory maps]"
|
||||
|
||||
+ '(multiline)' # multiline options
|
||||
'--multiline[permit matching across multiple lines]'
|
||||
$no"--no-multiline[restrict matches to at most one line each]"
|
||||
|
||||
+ '(only)' # Only-match options
|
||||
'(passthru replace)'{-o,--only-matching}'[show only matching part of each line]'
|
||||
|
||||
|
43
src/app.rs
43
src/app.rs
@ -2,8 +2,8 @@
|
||||
// including some light validation.
|
||||
//
|
||||
// This module is purposely written in a bare-bones way, since it is included
|
||||
// in ripgrep's build.rs file as a way to generate completion files for common
|
||||
// shells.
|
||||
// in ripgrep's build.rs file as a way to generate a man page and completion
|
||||
// files for common shells.
|
||||
//
|
||||
// The only other place that ripgrep deals with clap is in src/args.rs, which
|
||||
// is where we read clap's configuration from the end user's arguments and turn
|
||||
@ -478,7 +478,7 @@ impl RGArg {
|
||||
}
|
||||
}
|
||||
|
||||
// We add an extra space to long descriptions so that a black line is inserted
|
||||
// We add an extra space to long descriptions so that a blank line is inserted
|
||||
// between flag descriptions in --help output.
|
||||
macro_rules! long {
|
||||
($lit:expr) => { concat!($lit, " ") }
|
||||
@ -525,6 +525,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
||||
flag_max_depth(&mut args);
|
||||
flag_max_filesize(&mut args);
|
||||
flag_mmap(&mut args);
|
||||
flag_multiline(&mut args);
|
||||
flag_no_config(&mut args);
|
||||
flag_no_ignore(&mut args);
|
||||
flag_no_ignore_global(&mut args);
|
||||
@ -813,10 +814,23 @@ fn flag_debug(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Show debug messages.";
|
||||
const LONG: &str = long!("\
|
||||
Show debug messages. Please use this when filing a bug report.
|
||||
|
||||
The --debug flag is generally useful for figuring out why ripgrep skipped
|
||||
searching a particular file. The debug messages should mention all files
|
||||
skipped and why they were skipped.
|
||||
|
||||
To get even more debug output, use the --trace flag, which implies --debug
|
||||
along with additional trace data. With --trace, the output could be quite
|
||||
large and is generally more useful for development.
|
||||
");
|
||||
let arg = RGArg::switch("debug")
|
||||
.help(SHORT).long_help(LONG);
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("trace")
|
||||
.hidden()
|
||||
.overrides("debug");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_dfa_size_limit(args: &mut Vec<RGArg>) {
|
||||
@ -1198,6 +1212,24 @@ This flag overrides --mmap.
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_multiline(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Enable matching across multiple lines.";
|
||||
const LONG: &str = long!("\
|
||||
Enable matching across multiple lines.
|
||||
|
||||
This flag can be disabled with --no-multiline.
|
||||
");
|
||||
let arg = RGArg::switch("multiline")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.overrides("no-multiline");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("no-multiline")
|
||||
.hidden()
|
||||
.overrides("multiline");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_no_config(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Never read configuration files.";
|
||||
const LONG: &str = long!("\
|
||||
@ -1374,13 +1406,10 @@ the empty string. For example, if you are searching using 'rg foo' then using
|
||||
'rg \"^|foo\"' instead will emit every line in every file searched, but only
|
||||
occurrences of 'foo' will be highlighted. This flag enables the same behavior
|
||||
without needing to modify the pattern.
|
||||
|
||||
This flag conflicts with the --only-matching and --replace flags.
|
||||
");
|
||||
let arg = RGArg::switch("passthru")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.alias("passthrough")
|
||||
.conflicts(&["only-matching", "replace"]);
|
||||
.alias("passthrough");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
|
27
src/args.rs
27
src/args.rs
@ -21,6 +21,7 @@ use atty;
|
||||
use ignore::overrides::{Override, OverrideBuilder};
|
||||
use ignore::types::{FileTypeDef, Types, TypesBuilder};
|
||||
use ignore;
|
||||
use messages::{set_messages, set_ignore_messages};
|
||||
use printer::{ColorSpecs, Printer};
|
||||
use unescape::{escape, unescape};
|
||||
use worker::{Worker, WorkerBuilder};
|
||||
@ -64,10 +65,8 @@ pub struct Args {
|
||||
mmap: bool,
|
||||
no_ignore: bool,
|
||||
no_ignore_global: bool,
|
||||
no_ignore_messages: bool,
|
||||
no_ignore_parent: bool,
|
||||
no_ignore_vcs: bool,
|
||||
no_messages: bool,
|
||||
null: bool,
|
||||
only_matching: bool,
|
||||
path_separator: Option<u8>,
|
||||
@ -101,6 +100,8 @@ impl Args {
|
||||
// arguments, then we re-parse argv, otherwise we just use the matches
|
||||
// we have here.
|
||||
let early_matches = ArgMatches(app::app().get_matches());
|
||||
set_messages(!early_matches.is_present("no-messages"));
|
||||
set_ignore_messages(!early_matches.is_present("no-ignore-messages"));
|
||||
|
||||
if let Err(err) = Logger::init() {
|
||||
errored!("failed to initialize logger: {}", err);
|
||||
@ -120,6 +121,8 @@ impl Args {
|
||||
} else {
|
||||
log::set_max_level(log::LevelFilter::Warn);
|
||||
}
|
||||
set_messages(!matches.is_present("no-messages"));
|
||||
set_ignore_messages(!matches.is_present("no-ignore-messages"));
|
||||
matches.to_args()
|
||||
}
|
||||
|
||||
@ -137,7 +140,7 @@ impl Args {
|
||||
}
|
||||
// If the user wants ripgrep to use a config file, then parse args
|
||||
// from that first.
|
||||
let mut args = config::args(early_matches.is_present("no-messages"));
|
||||
let mut args = config::args();
|
||||
if args.is_empty() {
|
||||
return early_matches;
|
||||
}
|
||||
@ -286,7 +289,6 @@ impl Args {
|
||||
.invert_match(self.invert_match)
|
||||
.max_count(self.max_count)
|
||||
.mmap(self.mmap)
|
||||
.no_messages(self.no_messages)
|
||||
.quiet(self.quiet)
|
||||
.text(self.text)
|
||||
.search_zip_files(self.search_zip_files)
|
||||
@ -310,17 +312,6 @@ impl Args {
|
||||
self.type_list
|
||||
}
|
||||
|
||||
/// Returns true if error messages should be suppressed.
|
||||
pub fn no_messages(&self) -> bool {
|
||||
self.no_messages
|
||||
}
|
||||
|
||||
/// Returns true if error messages associated with parsing .ignore or
|
||||
/// .gitignore files should be suppressed.
|
||||
pub fn no_ignore_messages(&self) -> bool {
|
||||
self.no_ignore_messages
|
||||
}
|
||||
|
||||
/// Create a new recursive directory iterator over the paths in argv.
|
||||
pub fn walker(&self) -> ignore::Walk {
|
||||
self.walker_builder().build()
|
||||
@ -340,9 +331,7 @@ impl Args {
|
||||
}
|
||||
for path in &self.ignore_files {
|
||||
if let Some(err) = wd.add_ignore(path) {
|
||||
if !self.no_messages && !self.no_ignore_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
ignore_message!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -419,10 +408,8 @@ impl<'a> ArgMatches<'a> {
|
||||
mmap: mmap,
|
||||
no_ignore: self.no_ignore(),
|
||||
no_ignore_global: self.no_ignore_global(),
|
||||
no_ignore_messages: self.is_present("no-ignore-messages"),
|
||||
no_ignore_parent: self.no_ignore_parent(),
|
||||
no_ignore_vcs: self.no_ignore_vcs(),
|
||||
no_messages: self.is_present("no-messages"),
|
||||
null: self.is_present("null"),
|
||||
only_matching: self.is_present("only-matching"),
|
||||
path_separator: self.path_separator()?,
|
||||
|
1384
src/args2.rs
Normal file
1384
src/args2.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,10 +12,7 @@ use std::path::{Path, PathBuf};
|
||||
use Result;
|
||||
|
||||
/// Return a sequence of arguments derived from ripgrep rc configuration files.
|
||||
///
|
||||
/// If no_messages is false and there was a problem reading a config file,
|
||||
/// then errors are printed to stderr.
|
||||
pub fn args(no_messages: bool) -> Vec<OsString> {
|
||||
pub fn args() -> Vec<OsString> {
|
||||
let config_path = match env::var_os("RIPGREP_CONFIG_PATH") {
|
||||
None => return vec![],
|
||||
Some(config_path) => {
|
||||
@ -28,20 +25,20 @@ pub fn args(no_messages: bool) -> Vec<OsString> {
|
||||
let (args, errs) = match parse(&config_path) {
|
||||
Ok((args, errs)) => (args, errs),
|
||||
Err(err) => {
|
||||
if !no_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
message!("{}", err);
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
if !no_messages && !errs.is_empty() {
|
||||
if !errs.is_empty() {
|
||||
for err in errs {
|
||||
eprintln!("{}:{}", config_path.display(), err);
|
||||
message!("{}:{}", config_path.display(), err);
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
"{}: arguments loaded from config file: {:?}",
|
||||
config_path.display(), args);
|
||||
config_path.display(),
|
||||
args
|
||||
);
|
||||
args
|
||||
}
|
||||
|
||||
@ -59,7 +56,7 @@ fn parse<P: AsRef<Path>>(
|
||||
let path = path.as_ref();
|
||||
match File::open(&path) {
|
||||
Ok(file) => parse_reader(file),
|
||||
Err(err) => errored!("{}: {}", path.display(), err),
|
||||
Err(err) => Err(From::from(format!("{}: {}", path.display(), err))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,19 +34,30 @@ impl Log for Logger {
|
||||
match (record.file(), record.line()) {
|
||||
(Some(file), Some(line)) => {
|
||||
eprintln!(
|
||||
"{}/{}/{}:{}: {}",
|
||||
record.level(), record.target(),
|
||||
file, line, record.args());
|
||||
"{}|{}|{}:{}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
file,
|
||||
line,
|
||||
record.args()
|
||||
);
|
||||
}
|
||||
(Some(file), None) => {
|
||||
eprintln!(
|
||||
"{}/{}/{}: {}",
|
||||
record.level(), record.target(), file, record.args());
|
||||
"{}|{}|{}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
file,
|
||||
record.args()
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"{}/{}: {}",
|
||||
record.level(), record.target(), record.args());
|
||||
"{}|{}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.args()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
66
src/main.rs
66
src/main.rs
@ -1,3 +1,5 @@
|
||||
#![allow(dead_code, unused_imports, unused_mut, unused_variables)]
|
||||
|
||||
extern crate atty;
|
||||
extern crate bytecount;
|
||||
#[macro_use]
|
||||
@ -6,6 +8,7 @@ extern crate encoding_rs;
|
||||
extern crate encoding_rs_io;
|
||||
extern crate globset;
|
||||
extern crate grep;
|
||||
extern crate grep2;
|
||||
extern crate ignore;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
@ -39,31 +42,40 @@ macro_rules! errored {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod messages;
|
||||
|
||||
mod app;
|
||||
mod args;
|
||||
mod args2;
|
||||
mod config;
|
||||
mod decompressor;
|
||||
mod preprocessor;
|
||||
mod logger;
|
||||
mod main2;
|
||||
mod path_printer;
|
||||
mod pathutil;
|
||||
mod printer;
|
||||
mod search;
|
||||
mod search_buffer;
|
||||
mod search_stream;
|
||||
mod subject;
|
||||
mod unescape;
|
||||
mod worker;
|
||||
|
||||
pub type Result<T> = result::Result<T, Box<Error>>;
|
||||
|
||||
fn main() {
|
||||
reset_sigpipe();
|
||||
match Args::parse().map(Arc::new).and_then(run) {
|
||||
Ok(0) => process::exit(1),
|
||||
Ok(_) => process::exit(0),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
process::exit(2);
|
||||
}
|
||||
}
|
||||
main2::main2();
|
||||
// reset_sigpipe();
|
||||
// match Args::parse().map(Arc::new).and_then(run) {
|
||||
// Ok(0) => process::exit(1),
|
||||
// Ok(_) => process::exit(0),
|
||||
// Err(err) => {
|
||||
// eprintln!("{}", err);
|
||||
// process::exit(2);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn run(args: Arc<Args>) -> Result<u64> {
|
||||
@ -113,8 +125,6 @@ fn run_parallel(args: &Arc<Args>) -> Result<u64> {
|
||||
result,
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
None => return Continue,
|
||||
Some(dent) => dent,
|
||||
@ -145,11 +155,9 @@ fn run_parallel(args: &Arc<Args>) -> Result<u64> {
|
||||
Continue
|
||||
})
|
||||
});
|
||||
if !args.paths().is_empty() && paths_searched.load(Ordering::SeqCst) == 0 {
|
||||
if !args.no_messages() {
|
||||
if paths_searched.load(Ordering::SeqCst) == 0 {
|
||||
eprint_nothing_searched();
|
||||
}
|
||||
}
|
||||
let match_line_count = match_line_count.load(Ordering::SeqCst) as u64;
|
||||
let paths_searched = paths_searched.load(Ordering::SeqCst) as u64;
|
||||
let paths_matched = paths_matched.load(Ordering::SeqCst) as u64;
|
||||
@ -176,8 +184,6 @@ fn run_one_thread(args: &Arc<Args>) -> Result<u64> {
|
||||
result,
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
None => continue,
|
||||
Some(dent) => dent,
|
||||
@ -203,11 +209,9 @@ fn run_one_thread(args: &Arc<Args>) -> Result<u64> {
|
||||
paths_matched += 1;
|
||||
}
|
||||
}
|
||||
if !args.paths().is_empty() && paths_searched == 0 {
|
||||
if !args.no_messages() {
|
||||
if paths_searched == 0 {
|
||||
eprint_nothing_searched();
|
||||
}
|
||||
}
|
||||
if args.stats() {
|
||||
print_stats(
|
||||
match_line_count,
|
||||
@ -241,8 +245,6 @@ fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
|
||||
result,
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
tx.send(dent).unwrap();
|
||||
if args.quiet() {
|
||||
@ -263,8 +265,6 @@ fn run_files_one_thread(args: &Arc<Args>) -> Result<u64> {
|
||||
result,
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
None => continue,
|
||||
Some(dent) => dent,
|
||||
@ -293,21 +293,15 @@ fn get_or_log_dir_entry(
|
||||
result: result::Result<ignore::DirEntry, ignore::Error>,
|
||||
stdout_handle: Option<&same_file::Handle>,
|
||||
files_only: bool,
|
||||
no_messages: bool,
|
||||
no_ignore_messages: bool,
|
||||
) -> Option<ignore::DirEntry> {
|
||||
match result {
|
||||
Err(err) => {
|
||||
if !no_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
message!("{}", err);
|
||||
None
|
||||
}
|
||||
Ok(dent) => {
|
||||
if let Some(err) = dent.error() {
|
||||
if !no_messages && !no_ignore_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
ignore_message!("{}", err);
|
||||
}
|
||||
if dent.file_type().is_none() {
|
||||
return Some(dent); // entry is stdin
|
||||
@ -321,7 +315,7 @@ fn get_or_log_dir_entry(
|
||||
}
|
||||
// If we are redirecting stdout to a file, then don't search that
|
||||
// file.
|
||||
if !files_only && is_stdout_file(&dent, stdout_handle, no_messages) {
|
||||
if !files_only && is_stdout_file(&dent, stdout_handle) {
|
||||
return None;
|
||||
}
|
||||
Some(dent)
|
||||
@ -371,7 +365,6 @@ fn ignore_entry_is_file(dent: &ignore::DirEntry) -> bool {
|
||||
fn is_stdout_file(
|
||||
dent: &ignore::DirEntry,
|
||||
stdout_handle: Option<&same_file::Handle>,
|
||||
no_messages: bool,
|
||||
) -> bool {
|
||||
let stdout_handle = match stdout_handle {
|
||||
None => return false,
|
||||
@ -385,9 +378,7 @@ fn is_stdout_file(
|
||||
match same_file::Handle::from_path(dent.path()) {
|
||||
Ok(h) => stdout_handle == &h,
|
||||
Err(err) => {
|
||||
if !no_messages {
|
||||
eprintln!("{}: {}", dent.path().display(), err);
|
||||
}
|
||||
message!("{}: {}", dent.path().display(), err);
|
||||
false
|
||||
}
|
||||
}
|
||||
@ -407,7 +398,8 @@ fn maybe_dent_eq_handle(_: &ignore::DirEntry, _: &same_file::Handle) -> bool {
|
||||
}
|
||||
|
||||
fn eprint_nothing_searched() {
|
||||
eprintln!("No files were searched, which means ripgrep probably \
|
||||
message!(
|
||||
"No files were searched, which means ripgrep probably \
|
||||
applied a filter you didn't expect. \
|
||||
Try running again with --debug.");
|
||||
}
|
||||
|
263
src/main2.rs
Normal file
263
src/main2.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use std::io;
|
||||
use std::process;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
|
||||
use grep2::printer::Stats;
|
||||
use ignore::WalkState;
|
||||
|
||||
use args2::Args;
|
||||
use subject::Subject;
|
||||
use Result;
|
||||
|
||||
pub fn main2() {
|
||||
match Args::parse().and_then(run) {
|
||||
Ok(false) => process::exit(1),
|
||||
Ok(true) => process::exit(0),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(args: Args) -> Result<bool> {
|
||||
use args2::Command::*;
|
||||
|
||||
match args.command()? {
|
||||
Search => search(args),
|
||||
SearchParallel => search_parallel(args),
|
||||
SearchNever => Ok(false),
|
||||
Files => files(args),
|
||||
FilesParallel => files_parallel(args),
|
||||
Types => types(args),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
message!("{}: {}", subject.path().display(), err);
|
||||
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;
|
||||
}
|
||||
}
|
||||
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.printer().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.
|
||||
fn search_parallel(args: Args) -> Result<bool> {
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
|
||||
let quit_after_match = args.quit_after_match()?;
|
||||
let started_at = Instant::now();
|
||||
let subject_builder = Arc::new(args.subject_builder());
|
||||
let bufwtr = Arc::new(args.buffer_writer()?);
|
||||
let stats = Arc::new(args.stats()?.map(Mutex::new));
|
||||
let matched = Arc::new(AtomicBool::new(false));
|
||||
let mut searcher_err = None;
|
||||
args.walker_parallel()?.run(|| {
|
||||
let args = args.clone();
|
||||
let bufwtr = Arc::clone(&bufwtr);
|
||||
let stats = Arc::clone(&stats);
|
||||
let matched = Arc::clone(&matched);
|
||||
let subject_builder = Arc::clone(&subject_builder);
|
||||
let mut searcher = match args.search_worker(bufwtr.buffer()) {
|
||||
Ok(searcher) => searcher,
|
||||
Err(err) => {
|
||||
searcher_err = Some(err);
|
||||
return Box::new(move |_| {
|
||||
WalkState::Quit
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Box::new(move |result| {
|
||||
let subject = match subject_builder.build_from_result(result) {
|
||||
Some(subject) => subject,
|
||||
None => return WalkState::Continue,
|
||||
};
|
||||
searcher.printer().get_mut().clear();
|
||||
let search_result = match searcher.search(&subject) {
|
||||
Ok(search_result) => search_result,
|
||||
Err(err) => {
|
||||
message!("{}: {}", subject.path().display(), err);
|
||||
return WalkState::Continue;
|
||||
}
|
||||
};
|
||||
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;
|
||||
}
|
||||
// Otherwise, we continue on our merry way.
|
||||
message!("{}: {}", subject.path().display(), err);
|
||||
}
|
||||
if matched.load(SeqCst) && quit_after_match {
|
||||
WalkState::Quit
|
||||
} else {
|
||||
WalkState::Continue
|
||||
}
|
||||
})
|
||||
});
|
||||
if let Some(err) = searcher_err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
if let Some(ref locked_stats) = *stats {
|
||||
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.printer().print_stats(elapsed, &stats);
|
||||
}
|
||||
Ok(matched.load(SeqCst))
|
||||
}
|
||||
|
||||
/// The top-level entry point for listing files without searching them. This
|
||||
/// recursively steps through the file list (current directory by default) and
|
||||
/// prints each path sequentially using a single thread.
|
||||
fn files(args: Args) -> Result<bool> {
|
||||
let quit_after_match = args.quit_after_match()?;
|
||||
let subject_builder = args.subject_builder();
|
||||
let mut matched = false;
|
||||
let mut 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn files_parallel(args: Args) -> Result<bool> {
|
||||
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()?;
|
||||
let subject_builder = Arc::new(args.subject_builder());
|
||||
let mut path_printer = args.path_printer(args.stdout())?;
|
||||
let matched = Arc::new(AtomicBool::new(false));
|
||||
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())?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
args.walker_parallel()?.run(|| {
|
||||
let args = args.clone();
|
||||
let subject_builder = Arc::clone(&subject_builder);
|
||||
let matched = Arc::clone(&matched);
|
||||
let tx = tx.clone();
|
||||
|
||||
Box::new(move |result| {
|
||||
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,
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
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());
|
||||
}
|
||||
}
|
||||
Ok(matched.load(SeqCst))
|
||||
}
|
||||
|
||||
/// The top-level entry point for --type-list.
|
||||
fn types(args: Args) -> Result<bool> {
|
||||
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": ")?;
|
||||
|
||||
let mut first = true;
|
||||
for glob in def.globs() {
|
||||
if !first {
|
||||
stdout.write_all(b", ")?;
|
||||
}
|
||||
stdout.write_all(glob.as_bytes())?;
|
||||
first = false;
|
||||
}
|
||||
stdout.write_all(b"\n")?;
|
||||
}
|
||||
Ok(count > 0)
|
||||
}
|
50
src/messages.rs
Normal file
50
src/messages.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use std::sync::atomic::{ATOMIC_BOOL_INIT, AtomicBool, Ordering};
|
||||
|
||||
static MESSAGES: AtomicBool = ATOMIC_BOOL_INIT;
|
||||
static IGNORE_MESSAGES: AtomicBool = ATOMIC_BOOL_INIT;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! message {
|
||||
($($tt:tt)*) => {
|
||||
if ::messages::messages() {
|
||||
eprintln!($($tt)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ignore_message {
|
||||
($($tt:tt)*) => {
|
||||
if ::messages::messages() && ::messages::ignore_messages() {
|
||||
eprintln!($($tt)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if and only if messages should be shown.
|
||||
pub fn messages() -> bool {
|
||||
MESSAGES.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Set whether messages should be shown or not.
|
||||
///
|
||||
/// By default, they are not shown.
|
||||
pub fn set_messages(yes: bool) {
|
||||
MESSAGES.store(yes, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Returns true if and only if "ignore" related messages should be shown.
|
||||
pub fn ignore_messages() -> bool {
|
||||
IGNORE_MESSAGES.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Set whether "ignore" related messages should be shown or not.
|
||||
///
|
||||
/// By default, they are not shown.
|
||||
///
|
||||
/// Note that this is overridden if `messages` is disabled. Namely, if
|
||||
/// `messages` is disabled, then "ignore" messages are never shown, regardless
|
||||
/// of this setting.
|
||||
pub fn set_ignore_messages(yes: bool) {
|
||||
IGNORE_MESSAGES.store(yes, Ordering::SeqCst)
|
||||
}
|
101
src/path_printer.rs
Normal file
101
src/path_printer.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use grep2::printer::{ColorSpecs, PrinterPath};
|
||||
use termcolor::WriteColor;
|
||||
|
||||
/// A configuration for describing how paths should be written.
|
||||
#[derive(Clone, Debug)]
|
||||
struct Config {
|
||||
colors: ColorSpecs,
|
||||
separator: Option<u8>,
|
||||
terminator: u8,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
colors: ColorSpecs::default(),
|
||||
separator: None,
|
||||
terminator: b'\n',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for constructing things to search over.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathPrinterBuilder {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl PathPrinterBuilder {
|
||||
/// Return a new subject builder with a default configuration.
|
||||
pub fn new() -> PathPrinterBuilder {
|
||||
PathPrinterBuilder { config: Config::default() }
|
||||
}
|
||||
|
||||
/// Create a new path printer with the current configuration that writes
|
||||
/// paths to the given writer.
|
||||
pub fn build<W: WriteColor>(&self, wtr: W) -> PathPrinter<W> {
|
||||
PathPrinter {
|
||||
config: self.config.clone(),
|
||||
wtr: wtr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the color specification for this printer.
|
||||
///
|
||||
/// Currently, only the `path` component of the given specification is
|
||||
/// used.
|
||||
pub fn color_specs(
|
||||
&mut self,
|
||||
specs: ColorSpecs,
|
||||
) -> &mut PathPrinterBuilder {
|
||||
self.config.colors = specs;
|
||||
self
|
||||
}
|
||||
|
||||
/// A path separator.
|
||||
///
|
||||
/// When provided, the path's default separator will be replaced with
|
||||
/// the given separator.
|
||||
///
|
||||
/// This is not set by default, and the system's default path separator
|
||||
/// will be used.
|
||||
pub fn separator(&mut self, sep: Option<u8>) -> &mut PathPrinterBuilder {
|
||||
self.config.separator = sep;
|
||||
self
|
||||
}
|
||||
|
||||
/// A path terminator.
|
||||
///
|
||||
/// When printing a path, it will be by terminated by the given byte.
|
||||
///
|
||||
/// This is set to `\n` by default.
|
||||
pub fn terminator(&mut self, terminator: u8) -> &mut PathPrinterBuilder {
|
||||
self.config.terminator = terminator;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A printer for emitting paths to a writer, with optional color support.
|
||||
#[derive(Debug)]
|
||||
pub struct PathPrinter<W> {
|
||||
config: Config,
|
||||
wtr: W,
|
||||
}
|
||||
|
||||
impl<W: WriteColor> PathPrinter<W> {
|
||||
/// Write the given path to the underlying writer.
|
||||
pub fn write_path(&mut self, path: &Path) -> io::Result<()> {
|
||||
let ppath = PrinterPath::with_separator(path, self.config.separator);
|
||||
if !self.wtr.supports_color() {
|
||||
self.wtr.write_all(ppath.as_bytes())?;
|
||||
} else {
|
||||
self.wtr.set_color(self.config.colors.path())?;
|
||||
self.wtr.write_all(ppath.as_bytes())?;
|
||||
self.wtr.reset()?;
|
||||
}
|
||||
self.wtr.write_all(&[self.config.terminator])
|
||||
}
|
||||
}
|
@ -4,9 +4,6 @@ typically faster than the same operations as provided in `std::path`. In
|
||||
particular, we really want to avoid the costly operation of parsing the path
|
||||
into its constituent components. We give up on Windows, but on Unix, we deal
|
||||
with the raw bytes directly.
|
||||
|
||||
On large repositories (like chromium), this can have a ~25% performance
|
||||
improvement on just listing the files to search (!).
|
||||
*/
|
||||
use std::path::Path;
|
||||
|
||||
|
@ -3,8 +3,6 @@ use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{self, Stdio};
|
||||
|
||||
use Result;
|
||||
|
||||
/// PreprocessorReader provides an `io::Read` impl to read kids output.
|
||||
#[derive(Debug)]
|
||||
pub struct PreprocessorReader {
|
||||
@ -26,7 +24,7 @@ impl PreprocessorReader {
|
||||
pub fn from_cmd_path(
|
||||
cmd: PathBuf,
|
||||
path: &Path,
|
||||
) -> Result<PreprocessorReader> {
|
||||
) -> io::Result<PreprocessorReader> {
|
||||
let child = process::Command::new(&cmd)
|
||||
.arg(path)
|
||||
.stdin(Stdio::from(File::open(path)?))
|
||||
@ -34,10 +32,13 @@ impl PreprocessorReader {
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"error running preprocessor command '{}': {}",
|
||||
cmd.display(),
|
||||
err,
|
||||
),
|
||||
)
|
||||
})?;
|
||||
Ok(PreprocessorReader {
|
||||
|
372
src/search.rs
Normal file
372
src/search.rs
Normal file
@ -0,0 +1,372 @@
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use grep2::matcher::Matcher;
|
||||
use grep2::printer::{JSON, Standard, Summary, Stats};
|
||||
use grep2::regex::RegexMatcher;
|
||||
use grep2::searcher::Searcher;
|
||||
use termcolor::WriteColor;
|
||||
|
||||
use decompressor::{DecompressionReader, is_compressed};
|
||||
use preprocessor::PreprocessorReader;
|
||||
use subject::Subject;
|
||||
|
||||
/// The configuration for the search worker. Among a few other things, the
|
||||
/// configuration primarily controls the way we show search results to users
|
||||
/// at a very high level.
|
||||
#[derive(Clone, Debug)]
|
||||
struct Config {
|
||||
preprocessor: Option<PathBuf>,
|
||||
search_zip: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
preprocessor: None,
|
||||
search_zip: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for configuring and constructing a search worker.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SearchWorkerBuilder {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Default for SearchWorkerBuilder {
|
||||
fn default() -> SearchWorkerBuilder {
|
||||
SearchWorkerBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchWorkerBuilder {
|
||||
/// Create a new builder for configuring and constructing a search worker.
|
||||
pub fn new() -> SearchWorkerBuilder {
|
||||
SearchWorkerBuilder { config: Config::default() }
|
||||
}
|
||||
|
||||
/// Create a new search worker using the given searcher, matcher and
|
||||
/// printer.
|
||||
pub fn build<W: WriteColor>(
|
||||
&self,
|
||||
matcher: PatternMatcher,
|
||||
searcher: Searcher,
|
||||
printer: Printer<W>,
|
||||
) -> SearchWorker<W> {
|
||||
let config = self.config.clone();
|
||||
SearchWorker { config, matcher, searcher, printer }
|
||||
}
|
||||
|
||||
/// Set the path to a preprocessor command.
|
||||
///
|
||||
/// When this is set, instead of searching files directly, the given
|
||||
/// command will be run with the file path as the first argument, and the
|
||||
/// output of that command will be searched instead.
|
||||
pub fn preprocessor(
|
||||
&mut self,
|
||||
cmd: Option<PathBuf>,
|
||||
) -> &mut SearchWorkerBuilder {
|
||||
self.config.preprocessor = cmd;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable the decompression and searching of common compressed files.
|
||||
///
|
||||
/// When enabled, if a particular file path is recognized as a compressed
|
||||
/// file, then it is decompressed before searching.
|
||||
///
|
||||
/// Note that if a preprocessor command is set, then it overrides this
|
||||
/// setting.
|
||||
pub fn search_zip(&mut self, yes: bool) -> &mut SearchWorkerBuilder {
|
||||
self.config.search_zip = yes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of executing a search.
|
||||
///
|
||||
/// Generally speaking, the "result" of a search is sent to a printer, which
|
||||
/// writes results to an underlying writer such as stdout or a file. However,
|
||||
/// every search also has some aggregate statistics or meta data that may be
|
||||
/// useful to higher level routines.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SearchResult {
|
||||
has_match: bool,
|
||||
binary_byte_offset: Option<u64>,
|
||||
stats: Option<Stats>,
|
||||
}
|
||||
|
||||
impl SearchResult {
|
||||
/// Whether the search found a match or not.
|
||||
pub fn has_match(&self) -> bool {
|
||||
self.has_match
|
||||
}
|
||||
|
||||
/// Whether the search found binary data, and if so, the first absolute
|
||||
/// byte offset at which it was detected.
|
||||
///
|
||||
/// This always returns `None` if binary data detection is disabled, even
|
||||
/// when binary data is present.
|
||||
pub fn binary_byte_offset(&self) -> Option<u64> {
|
||||
self.binary_byte_offset
|
||||
}
|
||||
|
||||
/// Return aggregate search statistics for a single search, if available.
|
||||
///
|
||||
/// It can be expensive to compute statistics, so these are only present
|
||||
/// if explicitly enabled in the printer provided by the caller.
|
||||
pub fn stats(&self) -> Option<&Stats> {
|
||||
self.stats.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// The pattern matcher used by a search worker.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PatternMatcher {
|
||||
RustRegex(RegexMatcher),
|
||||
}
|
||||
|
||||
/// The printer used by a search worker.
|
||||
///
|
||||
/// The `W` type parameter refers to the type of the underlying writer.
|
||||
#[derive(Debug)]
|
||||
pub enum Printer<W> {
|
||||
/// Use the standard printer, which supports the classic grep-like format.
|
||||
Standard(Standard<W>),
|
||||
/// Use the summary printer, which supports aggregate displays of search
|
||||
/// results.
|
||||
Summary(Summary<W>),
|
||||
/// A JSON printer, which emits results in the JSON Lines format.
|
||||
JSON(JSON<W>),
|
||||
}
|
||||
|
||||
impl<W: WriteColor> Printer<W> {
|
||||
/// Print the given statistics to the underlying writer in a way that is
|
||||
/// consistent with this printer's format.
|
||||
///
|
||||
/// While `Stats` contains a duration itself, this only corresponds to the
|
||||
/// time spent searching, where as `total_duration` should roughly
|
||||
/// approximate the lifespan of the ripgrep process itself.
|
||||
pub fn print_stats(
|
||||
&mut self,
|
||||
total_duration: Duration,
|
||||
stats: &Stats,
|
||||
) -> io::Result<()> {
|
||||
match *self {
|
||||
Printer::JSON(_) => unimplemented!(),
|
||||
Printer::Standard(_) | Printer::Summary(_) => {
|
||||
self.print_stats_human(total_duration, stats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_stats_human(
|
||||
&mut self,
|
||||
total_duration: Duration,
|
||||
stats: &Stats,
|
||||
) -> io::Result<()> {
|
||||
let mut wtr = self.get_mut();
|
||||
|
||||
write!(
|
||||
wtr,
|
||||
"
|
||||
{matches} matches
|
||||
{lines} matched lines
|
||||
{searches_with_match} files contained matches
|
||||
{searches} files searched
|
||||
{bytes_printed} bytes printed
|
||||
{bytes_searched} bytes searched
|
||||
{search_time:.6} seconds spent searching
|
||||
{process_time:.6} seconds
|
||||
",
|
||||
matches = stats.matches(),
|
||||
lines = stats.matched_lines(),
|
||||
searches_with_match = stats.searches_with_match(),
|
||||
searches = stats.searches(),
|
||||
bytes_printed = stats.bytes_printed(),
|
||||
bytes_searched = stats.bytes_searched(),
|
||||
search_time = fractional_seconds(stats.elapsed()),
|
||||
process_time = fractional_seconds(total_duration)
|
||||
)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the underlying printer's writer.
|
||||
pub fn get_mut(&mut self) -> &mut W {
|
||||
match *self {
|
||||
Printer::Standard(ref mut p) => p.get_mut(),
|
||||
Printer::Summary(ref mut p) => p.get_mut(),
|
||||
Printer::JSON(ref mut p) => p.get_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A worker for executing searches.
|
||||
///
|
||||
/// It is intended for a single worker to execute many searches, and is
|
||||
/// generally intended to be used from a single thread. When searching using
|
||||
/// multiple threads, it is better to create a new worker for each thread.
|
||||
#[derive(Debug)]
|
||||
pub struct SearchWorker<W> {
|
||||
config: Config,
|
||||
matcher: PatternMatcher,
|
||||
searcher: Searcher,
|
||||
printer: Printer<W>,
|
||||
}
|
||||
|
||||
impl<W: WriteColor> SearchWorker<W> {
|
||||
/// Execute a search over the given subject.
|
||||
pub fn search(&mut self, subject: &Subject) -> io::Result<SearchResult> {
|
||||
self.search_impl(subject)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the underlying printer.
|
||||
pub fn printer(&mut self) -> &mut Printer<W> {
|
||||
&mut self.printer
|
||||
}
|
||||
|
||||
/// Search the given subject using the appropriate strategy.
|
||||
fn search_impl(&mut self, subject: &Subject) -> io::Result<SearchResult> {
|
||||
let path = subject.path();
|
||||
if subject.is_stdin() {
|
||||
let stdin = io::stdin();
|
||||
// A `return` here appeases the borrow checker. NLL will fix this.
|
||||
return self.search_reader(path, stdin.lock());
|
||||
} else if self.config.preprocessor.is_some() {
|
||||
let cmd = self.config.preprocessor.clone().unwrap();
|
||||
let rdr = PreprocessorReader::from_cmd_path(cmd, path)?;
|
||||
self.search_reader(path, rdr)
|
||||
} else if self.config.search_zip && is_compressed(path) {
|
||||
match DecompressionReader::from_path(path) {
|
||||
None => Ok(SearchResult::default()),
|
||||
Some(rdr) => self.search_reader(path, rdr),
|
||||
}
|
||||
} else {
|
||||
self.search_path(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Search the contents of the given file path.
|
||||
fn search_path(&mut self, path: &Path) -> io::Result<SearchResult> {
|
||||
use self::PatternMatcher::*;
|
||||
|
||||
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
||||
match self.matcher {
|
||||
RustRegex(ref m) => search_path(m, searcher, printer, path),
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a search on the given reader, which may or may not correspond
|
||||
/// directly to the contents of the given file path. Instead, the reader
|
||||
/// may actually cause something else to be searched (for example, when
|
||||
/// a preprocessor is set or when decompression is enabled). In those
|
||||
/// cases, the file path is used for visual purposes only.
|
||||
///
|
||||
/// Generally speaking, this method should only be used when there is no
|
||||
/// other choice. Searching via `search_path` provides more opportunities
|
||||
/// for optimizations (such as memory maps).
|
||||
fn search_reader<R: io::Read>(
|
||||
&mut self,
|
||||
path: &Path,
|
||||
rdr: R,
|
||||
) -> io::Result<SearchResult> {
|
||||
use self::PatternMatcher::*;
|
||||
|
||||
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
||||
match self.matcher {
|
||||
RustRegex(ref m) => search_reader(m, searcher, printer, path, rdr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Search the contents of the given file path using the given matcher,
|
||||
/// searcher and printer.
|
||||
fn search_path<M: Matcher, W: WriteColor>(
|
||||
matcher: M,
|
||||
searcher: &mut Searcher,
|
||||
printer: &mut Printer<W>,
|
||||
path: &Path,
|
||||
) -> io::Result<SearchResult> {
|
||||
match *printer {
|
||||
Printer::Standard(ref mut p) => {
|
||||
let mut sink = p.sink_with_path(&matcher, path);
|
||||
searcher.search_path(&matcher, path, &mut sink)?;
|
||||
Ok(SearchResult {
|
||||
has_match: sink.has_match(),
|
||||
binary_byte_offset: sink.binary_byte_offset(),
|
||||
stats: sink.stats().map(|s| s.clone()),
|
||||
..SearchResult::default()
|
||||
})
|
||||
}
|
||||
Printer::Summary(ref mut p) => {
|
||||
let mut sink = p.sink_with_path(&matcher, path);
|
||||
searcher.search_path(&matcher, path, &mut sink)?;
|
||||
Ok(SearchResult {
|
||||
has_match: sink.has_match(),
|
||||
binary_byte_offset: sink.binary_byte_offset(),
|
||||
stats: sink.stats().map(|s| s.clone()),
|
||||
..SearchResult::default()
|
||||
})
|
||||
}
|
||||
Printer::JSON(ref mut p) => {
|
||||
let mut sink = p.sink_with_path(&matcher, path);
|
||||
searcher.search_path(&matcher, path, &mut sink)?;
|
||||
Ok(SearchResult {
|
||||
has_match: sink.has_match(),
|
||||
binary_byte_offset: sink.binary_byte_offset(),
|
||||
stats: Some(sink.stats().clone()),
|
||||
..SearchResult::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Search the contents of the given reader using the given matcher, searcher
|
||||
/// and printer.
|
||||
fn search_reader<M: Matcher, R: io::Read, W: WriteColor>(
|
||||
matcher: M,
|
||||
searcher: &mut Searcher,
|
||||
printer: &mut Printer<W>,
|
||||
path: &Path,
|
||||
rdr: R,
|
||||
) -> io::Result<SearchResult> {
|
||||
match *printer {
|
||||
Printer::Standard(ref mut p) => {
|
||||
let mut sink = p.sink_with_path(&matcher, path);
|
||||
searcher.search_reader(&matcher, rdr, &mut sink)?;
|
||||
Ok(SearchResult {
|
||||
has_match: sink.has_match(),
|
||||
binary_byte_offset: sink.binary_byte_offset(),
|
||||
stats: sink.stats().map(|s| s.clone()),
|
||||
..SearchResult::default()
|
||||
})
|
||||
}
|
||||
Printer::Summary(ref mut p) => {
|
||||
let mut sink = p.sink_with_path(&matcher, path);
|
||||
searcher.search_reader(&matcher, rdr, &mut sink)?;
|
||||
Ok(SearchResult {
|
||||
has_match: sink.has_match(),
|
||||
binary_byte_offset: sink.binary_byte_offset(),
|
||||
stats: sink.stats().map(|s| s.clone()),
|
||||
..SearchResult::default()
|
||||
})
|
||||
}
|
||||
Printer::JSON(ref mut p) => {
|
||||
let mut sink = p.sink_with_path(&matcher, path);
|
||||
searcher.search_reader(&matcher, rdr, &mut sink)?;
|
||||
Ok(SearchResult {
|
||||
has_match: sink.has_match(),
|
||||
binary_byte_offset: sink.binary_byte_offset(),
|
||||
stats: Some(sink.stats().clone()),
|
||||
..SearchResult::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the given duration as fractional seconds.
|
||||
fn fractional_seconds(duration: Duration) -> f64 {
|
||||
(duration.as_secs() as f64) + (duration.subsec_nanos() as f64 * 1e-9)
|
||||
}
|
231
src/subject.rs
Normal file
231
src/subject.rs
Normal file
@ -0,0 +1,231 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ignore::{self, DirEntry};
|
||||
use same_file::Handle;
|
||||
|
||||
/// A configuration for describing how subjects should be built.
|
||||
#[derive(Clone, Debug)]
|
||||
struct Config {
|
||||
skip: Option<Arc<Handle>>,
|
||||
strip_dot_prefix: bool,
|
||||
separator: Option<u8>,
|
||||
terminator: Option<u8>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
skip: None,
|
||||
strip_dot_prefix: false,
|
||||
separator: None,
|
||||
terminator: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for constructing things to search over.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SubjectBuilder {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl SubjectBuilder {
|
||||
/// Return a new subject builder with a default configuration.
|
||||
pub fn new() -> SubjectBuilder {
|
||||
SubjectBuilder { config: Config::default() }
|
||||
}
|
||||
|
||||
/// Create a new subject from a possibly missing directory entry.
|
||||
///
|
||||
/// If the directory entry isn't present, then the corresponding error is
|
||||
/// logged if messages have been configured. Otherwise, if the subject is
|
||||
/// deemed searchable, then it is returned.
|
||||
pub fn build_from_result(
|
||||
&self,
|
||||
result: Result<DirEntry, ignore::Error>,
|
||||
) -> Option<Subject> {
|
||||
match result {
|
||||
Ok(dent) => self.build(dent),
|
||||
Err(err) => {
|
||||
message!("{}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new subject using this builder's configuration.
|
||||
///
|
||||
/// If a subject could not be created or should otherwise not be searched,
|
||||
/// then this returns `None` after emitting any relevant log messages.
|
||||
pub fn build(&self, dent: DirEntry) -> Option<Subject> {
|
||||
let subj = Subject {
|
||||
dent: dent,
|
||||
strip_dot_prefix: self.config.strip_dot_prefix,
|
||||
};
|
||||
if let Some(ignore_err) = subj.dent.error() {
|
||||
ignore_message!("{}", ignore_err);
|
||||
}
|
||||
// If this entry represents stdin, then we always search it.
|
||||
if subj.dent.is_stdin() {
|
||||
return Some(subj);
|
||||
}
|
||||
// If we're supposed to skip a particular file, then skip it.
|
||||
if let Some(ref handle) = self.config.skip {
|
||||
match subj.equals(handle) {
|
||||
Ok(false) => {} // fallthrough
|
||||
Ok(true) => {
|
||||
debug!(
|
||||
"ignoring {}: (probably same file as stdout)",
|
||||
subj.dent.path().display()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Err(err) => {
|
||||
message!("{}: {}", subj.dent.path().display(), err);
|
||||
debug!(
|
||||
"ignoring {}: got error: {}",
|
||||
subj.dent.path().display(), err
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If this subject has a depth of 0, then it was provided explicitly
|
||||
// by an end user (or via a shell glob). In this case, we always want
|
||||
// to search it if it even smells like a file (e.g., a symlink).
|
||||
if subj.dent.depth() == 0 && !subj.is_dir() {
|
||||
return Some(subj);
|
||||
}
|
||||
// At this point, we only want to search something it's explicitly a
|
||||
// file. This omits symlinks. (If ripgrep was configured to follow
|
||||
// symlinks, then they have already been followed by the directory
|
||||
// traversal.)
|
||||
if subj.is_file() {
|
||||
return Some(subj);
|
||||
}
|
||||
// We got nothin. Emit a debug message, but only if this isn't a
|
||||
// directory. Otherwise, emitting messages for directories is just
|
||||
// noisy.
|
||||
if !subj.is_dir() {
|
||||
debug!(
|
||||
"ignoring {}: failed to pass subject filter: \
|
||||
file type: {:?}, metadata: {:?}",
|
||||
subj.dent.path().display(),
|
||||
subj.dent.file_type(),
|
||||
subj.dent.metadata()
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// When provided, subjects that represent the same file as the handle
|
||||
/// given will be skipped.
|
||||
///
|
||||
/// Typically, it is useful to pass a handle referring to stdout, such
|
||||
/// that the file being written to isn't searched, which can lead to
|
||||
/// an unbounded feedback mechanism.
|
||||
///
|
||||
/// Only one handle to skip can be provided.
|
||||
pub fn skip(
|
||||
&mut self,
|
||||
handle: Option<Handle>,
|
||||
) -> &mut SubjectBuilder {
|
||||
self.config.skip = handle.map(Arc::new);
|
||||
self
|
||||
}
|
||||
|
||||
/// When enabled, if the subject's file path starts with `./` then it is
|
||||
/// stripped.
|
||||
///
|
||||
/// This is useful when implicitly searching the current working directory.
|
||||
pub fn strip_dot_prefix(&mut self, yes: bool) -> &mut SubjectBuilder {
|
||||
self.config.strip_dot_prefix = yes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A subject is a thing we want to search. Generally, a subject is either a
|
||||
/// file or stdin.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Subject {
|
||||
dent: DirEntry,
|
||||
strip_dot_prefix: bool,
|
||||
}
|
||||
|
||||
impl Subject {
|
||||
/// Return the file path corresponding to this subject.
|
||||
///
|
||||
/// If this subject corresponds to stdin, then a special `<stdin>` path
|
||||
/// is returned instead.
|
||||
pub fn path(&self) -> &Path {
|
||||
if self.strip_dot_prefix && self.dent.path().starts_with("./") {
|
||||
self.dent.path().strip_prefix("./").unwrap()
|
||||
} else {
|
||||
self.dent.path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if and only if this entry corresponds to stdin.
|
||||
pub fn is_stdin(&self) -> bool {
|
||||
self.dent.is_stdin()
|
||||
}
|
||||
|
||||
/// Returns true if and only if this subject points to a directory.
|
||||
///
|
||||
/// This works around a bug in Rust's standard library:
|
||||
/// https://github.com/rust-lang/rust/issues/46484
|
||||
#[cfg(windows)]
|
||||
fn is_dir(&self) -> bool {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
|
||||
|
||||
self.dent.metadata().map(|md| {
|
||||
md.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0
|
||||
}).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns true if and only if this subject points to a directory.
|
||||
#[cfg(not(windows))]
|
||||
fn is_dir(&self) -> bool {
|
||||
self.dent.file_type().map_or(false, |ft| ft.is_dir())
|
||||
}
|
||||
|
||||
/// Returns true if and only if this subject points to a file.
|
||||
///
|
||||
/// This works around a bug in Rust's standard library:
|
||||
/// https://github.com/rust-lang/rust/issues/46484
|
||||
#[cfg(windows)]
|
||||
fn is_file(&self) -> bool {
|
||||
!self.is_dir()
|
||||
}
|
||||
|
||||
/// Returns true if and only if this subject points to a file.
|
||||
#[cfg(not(windows))]
|
||||
fn is_file(&self) -> bool {
|
||||
self.dent.file_type().map_or(false, |ft| ft.is_file())
|
||||
}
|
||||
|
||||
/// Returns true if and only if this subject is believed to be equivalent
|
||||
/// to the given handle. If there was a problem querying this subject for
|
||||
/// information to determine equality, then that error is returned.
|
||||
fn equals(&self, handle: &Handle) -> io::Result<bool> {
|
||||
#[cfg(unix)]
|
||||
fn never_equal(dent: &DirEntry, handle: &Handle) -> bool {
|
||||
dent.ino() != Some(handle.ino())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn never_equal(_: &DirEntry, _: &Handle) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// If we know for sure that these two things aren't equal, then avoid
|
||||
// the costly extra stat call to determine equality.
|
||||
if self.dent.is_stdin() || never_equal(&self.dent, handle) {
|
||||
return Ok(false);
|
||||
}
|
||||
Handle::from_path(self.path()).map(|h| &h == handle)
|
||||
}
|
||||
}
|
@ -44,7 +44,6 @@ struct Options {
|
||||
invert_match: bool,
|
||||
line_number: bool,
|
||||
max_count: Option<u64>,
|
||||
no_messages: bool,
|
||||
quiet: bool,
|
||||
text: bool,
|
||||
preprocessor: Option<PathBuf>,
|
||||
@ -67,7 +66,6 @@ impl Default for Options {
|
||||
invert_match: false,
|
||||
line_number: false,
|
||||
max_count: None,
|
||||
no_messages: false,
|
||||
quiet: false,
|
||||
text: false,
|
||||
search_zip_files: false,
|
||||
@ -200,14 +198,6 @@ impl WorkerBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, error messages are suppressed.
|
||||
///
|
||||
/// This is disabled by default.
|
||||
pub fn no_messages(mut self, yes: bool) -> Self {
|
||||
self.opts.no_messages = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, don't show any output and quit searching after the first
|
||||
/// match is found.
|
||||
pub fn quiet(mut self, yes: bool) -> Self {
|
||||
@ -265,9 +255,7 @@ impl Worker {
|
||||
match PreprocessorReader::from_cmd_path(cmd, path) {
|
||||
Ok(reader) => self.search(printer, path, reader),
|
||||
Err(err) => {
|
||||
if !self.opts.no_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
message!("{}", err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -284,9 +272,7 @@ impl Worker {
|
||||
let file = match File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
if !self.opts.no_messages {
|
||||
eprintln!("{}: {}", path.display(), err);
|
||||
}
|
||||
message!("{}: {}", path.display(), err);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@ -306,9 +292,7 @@ impl Worker {
|
||||
count
|
||||
}
|
||||
Err(err) => {
|
||||
if !self.opts.no_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
message!("{}", err);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
336
tests/tests.rs
336
tests/tests.rs
@ -91,8 +91,8 @@ be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sherlock!(dir, "Sherlock", ".", |wd: WorkDir, mut cmd| {
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -148,19 +148,19 @@ sherlock!(with_heading_default, "Sherlock", ".",
|
||||
cmd.arg("-j1").arg("--heading");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected1 = "\
|
||||
foo
|
||||
./foo
|
||||
Sherlock Holmes lives on Baker Street.
|
||||
|
||||
sherlock
|
||||
./sherlock
|
||||
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
let expected2 = "\
|
||||
sherlock
|
||||
./sherlock
|
||||
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
|
||||
foo
|
||||
./foo
|
||||
Sherlock Holmes lives on Baker Street.
|
||||
";
|
||||
if lines != expected1 {
|
||||
@ -289,14 +289,14 @@ sherlock!(file_types, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.rs", "Sherlock");
|
||||
cmd.arg("-t").arg("rust");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.rs:Sherlock\n");
|
||||
assert_eq!(lines, "./file.rs:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(file_types_all, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.py", "Sherlock");
|
||||
cmd.arg("-t").arg("all");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.py:Sherlock\n");
|
||||
assert_eq!(lines, "./file.py:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(file_types_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
@ -305,7 +305,7 @@ sherlock!(file_types_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.rs", "Sherlock");
|
||||
cmd.arg("-T").arg("rust");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.py:Sherlock\n");
|
||||
assert_eq!(lines, "./file.py:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(file_types_negate_all, "Sherlock", ".",
|
||||
@ -315,8 +315,8 @@ sherlock!(file_types_negate_all, "Sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
");
|
||||
});
|
||||
|
||||
@ -333,18 +333,21 @@ sherlock!(file_type_add, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.wat", "Sherlock");
|
||||
cmd.arg("--type-add").arg("wat:*.wat").arg("-t").arg("wat");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.wat:Sherlock\n");
|
||||
assert_eq!(lines, "./file.wat:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(file_type_add_compose, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
sherlock!(file_type_add_compose, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.py", "Sherlock");
|
||||
wd.create("file.rs", "Sherlock");
|
||||
wd.create("file.wat", "Sherlock");
|
||||
cmd.arg("--type-add").arg("wat:*.wat");
|
||||
cmd.arg("--type-add").arg("combo:include:wat,py").arg("-t").arg("combo");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
println!("{}", lines);
|
||||
assert_eq!(sort_lines(&lines), "file.py:Sherlock\nfile.wat:Sherlock\n");
|
||||
assert_eq!(
|
||||
sort_lines(&lines),
|
||||
"./file.py:Sherlock\n./file.wat:Sherlock\n"
|
||||
);
|
||||
});
|
||||
|
||||
sherlock!(glob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
@ -352,7 +355,7 @@ sherlock!(glob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.rs", "Sherlock");
|
||||
cmd.arg("-g").arg("*.rs");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.rs:Sherlock\n");
|
||||
assert_eq!(lines, "./file.rs:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(glob_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
@ -361,14 +364,14 @@ sherlock!(glob_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.rs", "Sherlock");
|
||||
cmd.arg("-g").arg("!*.rs");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.py:Sherlock\n");
|
||||
assert_eq!(lines, "./file.py:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(iglob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.HTML", "Sherlock");
|
||||
cmd.arg("--iglob").arg("*.html");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.HTML:Sherlock\n");
|
||||
assert_eq!(lines, "./file.HTML:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(csglob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
@ -376,15 +379,16 @@ sherlock!(csglob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file2.html", "Sherlock");
|
||||
cmd.arg("--glob").arg("*.html");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file2.html:Sherlock\n");
|
||||
assert_eq!(lines, "./file2.html:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(byte_offset_only_matching, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
sherlock!(byte_offset_only_matching, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-b").arg("-o");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:56:Sherlock
|
||||
sherlock:177:Sherlock
|
||||
./sherlock:56:Sherlock
|
||||
./sherlock:177:Sherlock
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -392,35 +396,35 @@ sherlock:177:Sherlock
|
||||
sherlock!(count, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--count");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "sherlock:2\n";
|
||||
let expected = "./sherlock:2\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(count_matches, "the", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--count-matches");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "sherlock:4\n";
|
||||
let expected = "./sherlock:4\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(count_matches_inverted, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--count-matches").arg("--invert-match");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "sherlock:4\n";
|
||||
let expected = "./sherlock:4\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(count_matches_via_only, "the", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--count").arg("--only-matching");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "sherlock:4\n";
|
||||
let expected = "./sherlock:4\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(files_with_matches, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--files-with-matches");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "sherlock\n";
|
||||
let expected = "./sherlock\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -429,7 +433,7 @@ sherlock!(files_without_matches, "Sherlock", ".",
|
||||
wd.create("file.py", "foo");
|
||||
cmd.arg("--files-without-match");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "file.py\n";
|
||||
let expected = "./file.py\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -527,7 +531,7 @@ sherlock!(max_filesize_parse_no_suffix, "Sherlock", ".",
|
||||
cmd.arg("--max-filesize").arg("50").arg("--files");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
foo
|
||||
./foo
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -541,7 +545,7 @@ sherlock!(max_filesize_parse_k_suffix, "Sherlock", ".",
|
||||
cmd.arg("--max-filesize").arg("4K").arg("--files");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
foo
|
||||
./foo
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -555,7 +559,7 @@ sherlock!(max_filesize_parse_m_suffix, "Sherlock", ".",
|
||||
cmd.arg("--max-filesize").arg("1M").arg("--files");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
foo
|
||||
./foo
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -583,8 +587,8 @@ sherlock!(no_ignore_hidden, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--hidden");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
.sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
.sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./.sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./.sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -610,8 +614,8 @@ sherlock!(no_ignore, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--no-ignore");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -653,8 +657,8 @@ sherlock!(ignore_git_parent_stop, "Sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -686,8 +690,8 @@ sherlock!(ignore_git_parent_stop_file, "Sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -740,8 +744,8 @@ sherlock!(no_parent_ignore_git, "Sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -771,8 +775,8 @@ sherlock!(symlink_follow, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
baz/sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
baz/sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./baz/sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./baz/sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, path(expected));
|
||||
});
|
||||
@ -783,8 +787,8 @@ sherlock!(unrestricted1, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -796,8 +800,8 @@ sherlock!(unrestricted2, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
.sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
.sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./.sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./.sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -807,7 +811,7 @@ sherlock!(unrestricted3, "foo", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-uuu");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file:foo\x00bar\nfile:foo\x00baz\n");
|
||||
assert_eq!(lines, "./file:foo\x00bar\n./file:foo\x00baz\n");
|
||||
});
|
||||
|
||||
sherlock!(vimgrep, "Sherlock|Watson", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
@ -815,10 +819,10 @@ sherlock!(vimgrep, "Sherlock|Watson", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:3:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sherlock:5:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||
./sherlock:1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:3:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:5:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -829,10 +833,10 @@ sherlock!(vimgrep_no_line, "Sherlock|Watson", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sherlock:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||
./sherlock:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -843,10 +847,10 @@ sherlock!(vimgrep_no_line_no_column, "Sherlock|Watson", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sherlock:but Doctor Watson has to have it taken out for him and dusted,
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:but Doctor Watson has to have it taken out for him and dusted,
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -869,12 +873,12 @@ clean!(regression_25, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("src/llvm/foo", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("src/llvm/foo:test\n");
|
||||
let expected = path("./src/llvm/foo:test\n");
|
||||
assert_eq!(lines, expected);
|
||||
|
||||
cmd.current_dir(wd.path().join("src"));
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("llvm/foo:test\n");
|
||||
let expected = path("./llvm/foo:test\n");
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -885,7 +889,7 @@ clean!(regression_30, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("vendor/manifest", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("vendor/manifest:test\n");
|
||||
let expected = path("./vendor/manifest:test\n");
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -927,7 +931,7 @@ clean!(regression_67, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("dir/bar", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, path("dir/bar:test\n"));
|
||||
assert_eq!(lines, path("./dir/bar:test\n"));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/87
|
||||
@ -945,7 +949,7 @@ clean!(regression_90, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".foo", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, ".foo:test\n");
|
||||
assert_eq!(lines, "./.foo:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/93
|
||||
@ -954,7 +958,7 @@ clean!(regression_93, r"(\d{1,3}\.){3}\d{1,3}", ".",
|
||||
wd.create("foo", "192.168.1.1");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:192.168.1.1\n");
|
||||
assert_eq!(lines, "./foo:192.168.1.1\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/99
|
||||
@ -966,7 +970,10 @@ clean!(regression_99, "test", ".",
|
||||
cmd.arg("-j1").arg("--heading");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(sort_lines(&lines), sort_lines("bar\ntest\n\nfoo1\ntest\n"));
|
||||
assert_eq!(
|
||||
sort_lines(&lines),
|
||||
sort_lines("./bar\ntest\n\n./foo1\ntest\n")
|
||||
);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/105
|
||||
@ -975,7 +982,7 @@ clean!(regression_105_part1, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--vimgrep");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:1:3:zztest\n");
|
||||
assert_eq!(lines, "./foo:1:3:zztest\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/105
|
||||
@ -984,7 +991,7 @@ clean!(regression_105_part2, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--column");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:1:3:zztest\n");
|
||||
assert_eq!(lines, "./foo:1:3:zztest\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/127
|
||||
@ -1009,8 +1016,8 @@ clean!(regression_127, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = format!("\
|
||||
{path}:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
{path}:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./{path}:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./{path}:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
", path=path("foo/watson"));
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1021,7 +1028,7 @@ clean!(regression_128, "x", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-n");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:5:x\n");
|
||||
assert_eq!(lines, "./foo:5:x\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/131
|
||||
@ -1049,8 +1056,8 @@ sherlock!(regression_137, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sym1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sym1:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sym2:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
@ -1094,11 +1101,11 @@ clean!(regression_184, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo/bar/baz", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, format!("{}:test\n", path("foo/bar/baz")));
|
||||
assert_eq!(lines, format!("./{}:test\n", path("foo/bar/baz")));
|
||||
|
||||
cmd.current_dir(wd.path().join("./foo/bar"));
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "baz:test\n");
|
||||
assert_eq!(lines, "./baz:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/199
|
||||
@ -1107,7 +1114,7 @@ clean!(regression_199, r"\btest\b", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--smart-case");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:tEsT\n");
|
||||
assert_eq!(lines, "./foo:tEsT\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/206
|
||||
@ -1117,7 +1124,7 @@ clean!(regression_206, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-g").arg("*.txt");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, format!("{}:test\n", path("foo/bar.txt")));
|
||||
assert_eq!(lines, format!("./{}:test\n", path("foo/bar.txt")));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/210
|
||||
@ -1161,7 +1168,7 @@ clean!(regression_251, "привет", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-i");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:привет\nfoo:Привет\nfoo:ПрИвЕт\n");
|
||||
assert_eq!(lines, "./foo:привет\n./foo:Привет\n./foo:ПрИвЕт\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/256
|
||||
@ -1205,7 +1212,7 @@ clean!(regression_405, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-g").arg("!/foo/**");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, format!("{}:test\n", path("bar/foo/file2.txt")));
|
||||
assert_eq!(lines, format!("./{}:test\n", path("bar/foo/file2.txt")));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/428
|
||||
@ -1220,7 +1227,7 @@ clean!(regression_428_color_context_path, "foo", ".",
|
||||
let expected = format!(
|
||||
"{colored_path}:foo\n{colored_path}-bar\n",
|
||||
colored_path=format!(
|
||||
"\x1b\x5b\x30\x6d\x1b\x5b\x33\x35\x6d{path}\x1b\x5b\x30\x6d",
|
||||
"\x1b\x5b\x30\x6d\x1b\x5b\x33\x35\x6d./{path}\x1b\x5b\x30\x6d",
|
||||
path=path("sherlock")));
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1234,16 +1241,17 @@ clean!(regression_428_unrecognized_style, "Sherlok", ".",
|
||||
let output = cmd.output().unwrap();
|
||||
let err = String::from_utf8_lossy(&output.stderr);
|
||||
let expected = "\
|
||||
Unrecognized style attribute ''. Choose from: nobold, bold, nointense, intense, \
|
||||
unrecognized style attribute ''. Choose from: nobold, bold, nointense, intense, \
|
||||
nounderline, underline.
|
||||
";
|
||||
assert_eq!(err, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/493
|
||||
clean!(regression_493, " 're ", "input.txt", |wd: WorkDir, mut cmd: Command| {
|
||||
clean!(regression_493, r"\b 're \b", "input.txt",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("input.txt", "peshwaship 're seminomata");
|
||||
cmd.arg("-o").arg("-w");
|
||||
cmd.arg("-o");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, " 're \n");
|
||||
@ -1255,8 +1263,8 @@ sherlock!(regression_553_switch, "sherlock", ".",
|
||||
cmd.arg("-i");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
|
||||
@ -1264,8 +1272,8 @@ sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
cmd.arg("-i");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1305,12 +1313,9 @@ clean!(regression_599, "^$", "input.txt", |wd: WorkDir, mut cmd: Command| {
|
||||
]);
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
// Technically, the expected output should only be two lines, but:
|
||||
// https://github.com/BurntSushi/ripgrep/issues/441
|
||||
let expected = "\
|
||||
[0m1[0m:[0m[31m[0m
|
||||
[0m2[0m:[0m[31m[0m
|
||||
[0m4[0m:
|
||||
";
|
||||
assert_eq!(expected, lines);
|
||||
});
|
||||
@ -1326,7 +1331,7 @@ clean!(regression_807, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
cmd.arg("--hidden");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, format!("{}:test\n", path(".a/c/file")));
|
||||
assert_eq!(lines, format!("./{}:test\n", path(".a/c/file")));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/900
|
||||
@ -1343,7 +1348,7 @@ clean!(feature_1_sjis, "Шерлок Холмс", ".", |wd: WorkDir, mut cmd: Co
|
||||
cmd.arg("-Esjis");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||
assert_eq!(lines, "./foo:Шерлок Холмс\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||
@ -1354,7 +1359,7 @@ clean!(feature_1_utf16_auto, "Шерлок Холмс", ".",
|
||||
wd.create_bytes("foo", &sherlock[..]);
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||
assert_eq!(lines, "./foo:Шерлок Холмс\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||
@ -1366,7 +1371,7 @@ clean!(feature_1_utf16_explicit, "Шерлок Холмс", ".",
|
||||
cmd.arg("-Eutf-16le");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||
assert_eq!(lines, "./foo:Шерлок Холмс\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||
@ -1378,7 +1383,7 @@ clean!(feature_1_eucjp, "Шерлок Холмс", ".",
|
||||
cmd.arg("-Eeuc-jp");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||
assert_eq!(lines, "./foo:Шерлок Холмс\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||
@ -1413,8 +1418,8 @@ sherlock!(feature_7_dash, "-f-", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
let output = wd.pipe(&mut cmd, "Sherlock");
|
||||
let lines = String::from_utf8_lossy(&output.stdout);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1439,8 +1444,8 @@ sherlock!(feature_34_only_matching, "Sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:Sherlock
|
||||
sherlock:Sherlock
|
||||
./sherlock:Sherlock
|
||||
./sherlock:Sherlock
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1452,8 +1457,8 @@ sherlock!(feature_34_only_matching_line_column, "Sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:1:57:Sherlock
|
||||
sherlock:3:49:Sherlock
|
||||
./sherlock:1:57:Sherlock
|
||||
./sherlock:3:49:Sherlock
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1476,15 +1481,15 @@ sherlock!(feature_45_relative_cwd, "test", ".",
|
||||
// First, get a baseline without applying ignore rules.
|
||||
let lines = paths_from_stdout(wd.stdout(&mut cmd));
|
||||
assert_eq!(lines, paths(&[
|
||||
"bar/test", "baz/bar/test", "baz/baz/bar/test", "baz/foo",
|
||||
"baz/test", "foo", "test",
|
||||
"./bar/test", "./baz/bar/test", "./baz/baz/bar/test", "./baz/foo",
|
||||
"./baz/test", "./foo", "./test",
|
||||
]));
|
||||
|
||||
// Now try again with the ignore file activated.
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore");
|
||||
let lines = paths_from_stdout(wd.stdout(&mut cmd));
|
||||
assert_eq!(lines, paths(&[
|
||||
"baz/bar/test", "baz/baz/bar/test", "baz/test", "test",
|
||||
"./baz/bar/test", "./baz/baz/bar/test", "./baz/test", "./test",
|
||||
]));
|
||||
|
||||
// Now do it again, but inside the baz directory.
|
||||
@ -1496,7 +1501,7 @@ sherlock!(feature_45_relative_cwd, "test", ".",
|
||||
cmd.arg("test").arg(".").arg("--ignore-file").arg("../.not-an-ignore");
|
||||
cmd.current_dir(wd.path().join("baz"));
|
||||
let lines = paths_from_stdout(wd.stdout(&mut cmd));
|
||||
assert_eq!(lines, paths(&["baz/bar/test", "test"]));
|
||||
assert_eq!(lines, paths(&["./baz/bar/test", "./test"]));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||
@ -1509,7 +1514,7 @@ sherlock!(feature_45_precedence_with_others, "test", ".",
|
||||
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "imp.log:test\n");
|
||||
assert_eq!(lines, "./imp.log:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||
@ -1523,7 +1528,7 @@ sherlock!(feature_45_precedence_internal, "test", ".",
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore1");
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore2");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "imp.log:test\n");
|
||||
assert_eq!(lines, "./imp.log:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/68
|
||||
@ -1535,7 +1540,7 @@ clean!(feature_68_no_ignore_vcs, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--no-ignore-vcs");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:test\n");
|
||||
assert_eq!(lines, "./foo:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/70
|
||||
@ -1545,8 +1550,8 @@ sherlock!(feature_70_smart_case, "sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1557,7 +1562,7 @@ sherlock!(feature_89_files_with_matches, "Sherlock", ".",
|
||||
cmd.arg("--null").arg("--files-with-matches");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "sherlock\x00");
|
||||
assert_eq!(lines, "./sherlock\x00");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
@ -1567,7 +1572,7 @@ sherlock!(feature_89_files_without_matches, "Sherlock", ".",
|
||||
cmd.arg("--null").arg("--files-without-match");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.py\x00");
|
||||
assert_eq!(lines, "./file.py\x00");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
@ -1576,7 +1581,7 @@ sherlock!(feature_89_count, "Sherlock", ".",
|
||||
cmd.arg("--null").arg("--count");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "sherlock\x002\n");
|
||||
assert_eq!(lines, "./sherlock\x002\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
@ -1585,7 +1590,7 @@ sherlock!(feature_89_files, "NADA", ".",
|
||||
cmd.arg("--null").arg("--files");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "sherlock\x00");
|
||||
assert_eq!(lines, "./sherlock\x00");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
@ -1595,10 +1600,10 @@ sherlock!(feature_89_match, "Sherlock", ".",
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock\x00For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock\x00Holmeses, success in the province of detective work must always
|
||||
sherlock\x00be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sherlock\x00can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||
./sherlock\x00For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
./sherlock\x00Holmeses, success in the province of detective work must always
|
||||
./sherlock\x00be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
./sherlock\x00can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
@ -1613,7 +1618,7 @@ clean!(feature_109_max_depth, "far", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--maxdepth").arg("2");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("one/pass:far\n");
|
||||
let expected = path("./one/pass:far\n");
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -1639,7 +1644,7 @@ clean!(feature_129_matches, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-M26");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "foo:test\nfoo:[Omitted long line with 2 matches]\n";
|
||||
let expected = "./foo:test\n./foo:[Omitted long matching line]\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -1649,7 +1654,7 @@ clean!(feature_129_context, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-M20").arg("-C1");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "foo:test\nfoo-[Omitted long context line]\n";
|
||||
let expected = "./foo:test\n./foo-[Omitted long context line]\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -1659,7 +1664,7 @@ clean!(feature_129_replace, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-M26").arg("-rfoo");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "foo:foo\nfoo:[Omitted long line with 2 replacements]\n";
|
||||
let expected = "./foo:foo\n./foo:[Omitted long line with 2 matches]\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
@ -1668,7 +1673,7 @@ clean!(feature_159_works, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo", "test\ntest");
|
||||
cmd.arg("-m1");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:test\n");
|
||||
assert_eq!(lines, "./foo:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
||||
@ -1684,7 +1689,7 @@ clean!(feature_243_column_line, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--column");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:1:1:test\n");
|
||||
assert_eq!(lines, "./foo:1:1:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/263
|
||||
@ -1696,7 +1701,7 @@ clean!(feature_263_sort_files, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--sort-files");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "abc:test\nbar:test\nfoo:test\nzoo:test\n");
|
||||
assert_eq!(lines, "./abc:test\n./bar:test\n./foo:test\n./zoo:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/275
|
||||
@ -1706,7 +1711,7 @@ clean!(feature_275_pathsep, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--path-separator").arg("Z");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "fooZbar:test\n");
|
||||
assert_eq!(lines, ".ZfooZbar:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/362
|
||||
@ -1746,7 +1751,7 @@ sherlock!(feature_419_zero_as_shortcut_for_null, "Sherlock", ".",
|
||||
cmd.arg("-0").arg("--count");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "sherlock\x002\n");
|
||||
assert_eq!(lines, "./sherlock\x002\n");
|
||||
});
|
||||
|
||||
#[test]
|
||||
@ -1932,59 +1937,52 @@ fn feature_411_parallel_search_stats() {
|
||||
assert_eq!(lines.contains("seconds"), true);
|
||||
}
|
||||
|
||||
sherlock!(feature_411_ignore_stats_1, |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--files-with-matches");
|
||||
cmd.arg("--stats");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines.contains("seconds"), false);
|
||||
});
|
||||
|
||||
sherlock!(feature_411_ignore_stats_2, |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--files-without-match");
|
||||
cmd.arg("--stats");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines.contains("seconds"), false);
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn feature_740_passthru() {
|
||||
let wd = WorkDir::new("feature_740");
|
||||
wd.create("file", "\nfoo\nbar\nfoobar\n\nbaz\n");
|
||||
wd.create("patterns", "foo\n\nbar\n");
|
||||
wd.create("patterns", "foo\nbar\n");
|
||||
|
||||
// We can't assume that the way colour specs are translated to ANSI
|
||||
// sequences will remain stable, and --replace doesn't currently work with
|
||||
// pass-through, so for now we don't actually test the match sub-strings
|
||||
let common_args = &["-n", "--passthru"];
|
||||
let expected = "\
|
||||
1:
|
||||
let foo_expected = "\
|
||||
1-
|
||||
2:foo
|
||||
3:bar
|
||||
3-bar
|
||||
4:foobar
|
||||
5:
|
||||
6:baz
|
||||
5-
|
||||
6-baz
|
||||
";
|
||||
|
||||
// With single pattern
|
||||
let mut cmd = wd.command();
|
||||
cmd.args(common_args).arg("foo").arg("file");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, expected);
|
||||
assert_eq!(lines, foo_expected);
|
||||
|
||||
let foo_bar_expected = "\
|
||||
1-
|
||||
2:foo
|
||||
3:bar
|
||||
4:foobar
|
||||
5-
|
||||
6-baz
|
||||
";
|
||||
|
||||
// With multiple -e patterns
|
||||
let mut cmd = wd.command();
|
||||
cmd.args(common_args)
|
||||
.arg("-e").arg("foo").arg("-e").arg("bar").arg("file");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, expected);
|
||||
assert_eq!(lines, foo_bar_expected);
|
||||
|
||||
// With multiple -f patterns
|
||||
let mut cmd = wd.command();
|
||||
cmd.args(common_args).arg("-f").arg("patterns").arg("file");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, expected);
|
||||
assert_eq!(lines, foo_bar_expected);
|
||||
|
||||
// -c should override
|
||||
let mut cmd = wd.command();
|
||||
@ -1992,15 +1990,35 @@ fn feature_740_passthru() {
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "2\n");
|
||||
|
||||
let only_foo_expected = "\
|
||||
1-
|
||||
2:foo
|
||||
3-bar
|
||||
4:foo
|
||||
5-
|
||||
6-baz
|
||||
";
|
||||
|
||||
// -o should conflict
|
||||
let mut cmd = wd.command();
|
||||
cmd.args(common_args).arg("-o").arg("foo").arg("file");
|
||||
wd.assert_err(&mut cmd);
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, only_foo_expected);
|
||||
|
||||
let replace_foo_expected = "\
|
||||
1-
|
||||
2:wat
|
||||
3-bar
|
||||
4:watbar
|
||||
5-
|
||||
6-baz
|
||||
";
|
||||
|
||||
// -r should conflict
|
||||
let mut cmd = wd.command();
|
||||
cmd.args(common_args).arg("-r").arg("$0").arg("foo").arg("file");
|
||||
wd.assert_err(&mut cmd);
|
||||
cmd.args(common_args).arg("-r").arg("wat").arg("foo").arg("file");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, replace_foo_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2081,7 +2099,7 @@ fn regression_270() {
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("-e").arg("-test").arg("./");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, path("foo:-test\n"));
|
||||
assert_eq!(lines, path("./foo:-test\n"));
|
||||
}
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/391
|
||||
@ -2232,8 +2250,8 @@ fn regression_693_context_option_in_contextless_mode() {
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
bar:1
|
||||
foo:1
|
||||
./bar:1
|
||||
./foo:1
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user