summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Gallant <jamslam@gmail.com>2016-11-05 21:44:15 -0400
committerAndrew Gallant <jamslam@gmail.com>2016-11-05 21:45:55 -0400
commitb272be25fa7b616689384d2e4bec3e01715e2477 (patch)
tree8743d1ec86294f81da800c024f094a4846158527 /src
parent1aeae3e22da4e9ffa78ece485369e7a24744b2d4 (diff)
Add parallel recursive directory iterator.
This adds a new walk type in the `ignore` crate, `WalkParallel`, which provides a way for recursively iterating over a set of paths in parallel while respecting various ignore rules. The API is a bit strange, as a closure producing a closure isn't something one often sees, but it does seem to work well. This also allowed us to simplify much of the worker logic in ripgrep proper, where MultiWorker is now gone.
Diffstat (limited to 'src')
-rw-r--r--src/args.rs105
-rw-r--r--src/main.rs407
-rw-r--r--src/printer.rs1
-rw-r--r--src/worker.rs253
4 files changed, 447 insertions, 319 deletions
diff --git a/src/args.rs b/src/args.rs
index 9d2923b8..017ade4c 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -1,4 +1,3 @@
-use std::cmp;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
@@ -21,10 +20,9 @@ use ignore::types::{FileTypeDef, Types, TypesBuilder};
use ignore;
use out::{Out, ColoredTerminal};
use printer::Printer;
-use search_buffer::BufferSearcher;
-use search_stream::{InputBuffer, Searcher};
#[cfg(windows)]
use terminal_win::WindowsBuffer;
+use worker::{Worker, WorkerBuilder};
use Result;
@@ -364,7 +362,7 @@ impl RawArgs {
};
let threads =
if self.flag_threads == 0 {
- cmp::min(8, num_cpus::get())
+ num_cpus::get()
} else {
self.flag_threads
};
@@ -576,18 +574,6 @@ impl Args {
self.grep.clone()
}
- /// Creates a new input buffer that is used in searching.
- pub fn input_buffer(&self) -> InputBuffer {
- let mut inp = InputBuffer::new();
- inp.eol(self.eol);
- inp
- }
-
- /// Whether we should prefer memory maps for searching or not.
- pub fn mmap(&self) -> bool {
- self.mmap
- }
-
/// Whether ripgrep should be quiet or not.
pub fn quiet(&self) -> bool {
self.quiet
@@ -662,47 +648,27 @@ impl Args {
&self.paths
}
- /// Create a new line based searcher whose configuration is taken from the
- /// command line. This searcher supports a dizzying array of features:
- /// inverted matching, line counting, context control and more.
- pub fn searcher<'a, R: io::Read, W: Send + Terminal>(
- &self,
- inp: &'a mut InputBuffer,
- printer: &'a mut Printer<W>,
- grep: &'a Grep,
- path: &'a Path,
- rdr: R,
- ) -> Searcher<'a, R, W> {
- Searcher::new(inp, printer, grep, path, rdr)
- .after_context(self.after_context)
- .before_context(self.before_context)
- .count(self.count)
- .files_with_matches(self.files_with_matches)
- .eol(self.eol)
- .line_number(self.line_number)
- .invert_match(self.invert_match)
- .quiet(self.quiet)
- .text(self.text)
+ /// Returns true if there is exactly one file path given to search.
+ pub fn is_one_path(&self) -> bool {
+ self.paths.len() == 1
+ && (self.paths[0] == Path::new("-") || self.paths[0].is_file())
}
- /// Create a new line based searcher whose configuration is taken from the
- /// command line. This search operates on an entire file all once (which
- /// may have been memory mapped).
- pub fn searcher_buffer<'a, W: Send + Terminal>(
- &self,
- printer: &'a mut Printer<W>,
- grep: &'a Grep,
- path: &'a Path,
- buf: &'a [u8],
- ) -> BufferSearcher<'a, W> {
- BufferSearcher::new(printer, grep, path, buf)
+ /// Create a worker whose configuration is taken from the
+ /// command line.
+ pub fn worker(&self) -> Worker {
+ WorkerBuilder::new(self.grep())
+ .after_context(self.after_context)
+ .before_context(self.before_context)
.count(self.count)
.files_with_matches(self.files_with_matches)
.eol(self.eol)
.line_number(self.line_number)
.invert_match(self.invert_match)
+ .mmap(self.mmap)
.quiet(self.quiet)
.text(self.text)
+ .build()
}
/// Returns the number of worker search threads that should be used.
@@ -722,7 +688,17 @@ impl Args {
}
/// Create a new recursive directory iterator over the paths in argv.
- pub fn walker(&self) -> Walk {
+ pub fn walker(&self) -> ignore::Walk {
+ self.walker_builder().build()
+ }
+
+ /// Create a new parallel recursive directory iterator over the paths
+ /// in argv.
+ pub fn walker_parallel(&self) -> ignore::WalkParallel {
+ self.walker_builder().build_parallel()
+ }
+
+ fn walker_builder(&self) -> ignore::WalkBuilder {
let paths = self.paths();
let mut wd = ignore::WalkBuilder::new(&paths[0]);
for path in &paths[1..] {
@@ -744,7 +720,8 @@ impl Args {
wd.git_exclude(!self.no_ignore && !self.no_ignore_vcs);
wd.ignore(!self.no_ignore);
wd.parents(!self.no_ignore_parent);
- Walk(wd.build())
+ wd.threads(self.threads());
+ wd
}
}
@@ -761,34 +738,6 @@ fn version() -> String {
}
}
-/// A simple wrapper around the ignore::Walk iterator. This will
-/// automatically emit error messages to stderr and will skip directories.
-pub struct Walk(ignore::Walk);
-
-impl Iterator for Walk {
- type Item = ignore::DirEntry;
-
- fn next(&mut self) -> Option<ignore::DirEntry> {
- while let Some(result) = self.0.next() {
- match result {
- Ok(dent) => {
- if let Some(err) = dent.error() {
- eprintln!("{}", err);
- }
- if dent.file_type().map_or(false, |x| x.is_dir()) {
- continue;
- }
- return Some(dent);
- }
- Err(err) => {
- eprintln!("{}", err);
- }
- }
- }
- None
- }
-}
-
/// A single state in the state machine used by `unescape`.
#[derive(Clone, Copy, Eq, PartialEq)]
enum State {
diff --git a/src/main.rs b/src/main.rs
index 71c4da32..276ee059 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,4 @@
extern crate ctrlc;
-extern crate deque;
extern crate docopt;
extern crate env_logger;
extern crate grep;
@@ -21,30 +20,20 @@ extern crate term;
extern crate winapi;
use std::error::Error;
-use std::fs::File;
use std::io;
use std::io::Write;
-use std::path::Path;
use std::process;
use std::result;
use std::sync::{Arc, Mutex};
-use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use std::sync::mpsc;
use std::thread;
use std::cmp;
-use deque::{Stealer, Stolen};
-use grep::Grep;
-use memmap::{Mmap, Protection};
use term::Terminal;
-use ignore::DirEntry;
use args::Args;
-use out::{ColoredTerminal, Out};
-use pathutil::strip_prefix;
-use printer::Printer;
-use search_stream::InputBuffer;
-#[cfg(windows)]
-use terminal_win::WindowsBuffer;
+use worker::Work;
macro_rules! errored {
($($tt:tt)*) => {
@@ -68,11 +57,12 @@ mod search_buffer;
mod search_stream;
#[cfg(windows)]
mod terminal_win;
+mod worker;
pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
fn main() {
- match Args::parse().and_then(run) {
+ match Args::parse().map(Arc::new).and_then(run) {
Ok(count) if count == 0 => process::exit(1),
Ok(_) => process::exit(0),
Err(err) => {
@@ -82,95 +72,108 @@ fn main() {
}
}
-fn run(args: Args) -> Result<u64> {
- let args = Arc::new(args);
-
- let handler_args = args.clone();
- ctrlc::set_handler(move || {
- let stdout = io::stdout();
- let mut stdout = stdout.lock();
+fn run(args: Arc<Args>) -> Result<u64> {
+ {
+ let args = args.clone();
+ ctrlc::set_handler(move || {
+ let stdout = io::stdout();
+ let mut stdout = stdout.lock();
- let _ = handler_args.stdout().reset();
- let _ = stdout.flush();
+ let _ = args.stdout().reset();
+ let _ = stdout.flush();
- process::exit(1);
- });
-
- let paths = args.paths();
+ process::exit(1);
+ });
+ }
let threads = cmp::max(1, args.threads() - 1);
- let isone =
- paths.len() == 1 && (paths[0] == Path::new("-") || paths[0].is_file());
if args.files() {
- return run_files(args.clone());
- }
- if args.type_list() {
- return run_types(args.clone());
- }
- if threads == 1 || isone {
- return run_one_thread(args.clone());
+ if threads == 1 || args.is_one_path() {
+ run_files_one_thread(args)
+ } else {
+ run_files_parallel(args)
+ }
+ } else if args.type_list() {
+ run_types(args)
+ } else if threads == 1 || args.is_one_path() {
+ run_one_thread(args)
+ } else {
+ run_parallel(args)
}
+}
+
+fn run_parallel(args: Arc<Args>) -> Result<u64> {
let out = Arc::new(Mutex::new(args.out()));
let quiet_matched = QuietMatched::new(args.quiet());
- let mut workers = vec![];
+ let paths_searched = Arc::new(AtomicUsize::new(0));
+ let match_count = Arc::new(AtomicUsize::new(0));
+
+ args.walker_parallel().run(|| {
+ let args = args.clone();
+ let quiet_matched = quiet_matched.clone();
+ let paths_searched = paths_searched.clone();
+ let match_count = match_count.clone();
+ let out = out.clone();
+ let mut outbuf = args.outbuf();
+ let mut worker = args.worker();
+ Box::new(move |result| {
+ use ignore::WalkState::*;
- let workq = {
- let (workq, stealer) = deque::new();
- for _ in 0..threads {
- let worker = MultiWorker {
- chan_work: stealer.clone(),
- quiet_matched: quiet_matched.clone(),
- out: out.clone(),
- outbuf: Some(args.outbuf()),
- worker: Worker {
- args: args.clone(),
- inpbuf: args.input_buffer(),
- grep: args.grep(),
- match_count: 0,
- },
+ if quiet_matched.has_match() {
+ return Quit;
+ }
+ let dent = match get_or_log_dir_entry(result) {
+ None => return Continue,
+ Some(dent) => dent,
};
- workers.push(thread::spawn(move || worker.run()));
- }
- workq
- };
- let mut paths_searched: u64 = 0;
- for dent in args.walker() {
- if quiet_matched.has_match() {
- break;
- }
- paths_searched += 1;
- if dent.is_stdin() {
- workq.push(Work::Stdin);
- } else {
- workq.push(Work::File(dent));
- }
- }
- if !paths.is_empty() && paths_searched == 0 {
- eprintln!("No files were searched, which means ripgrep probably \
- applied a filter you didn't expect. \
- Try running again with --debug.");
- }
- for _ in 0..workers.len() {
- workq.push(Work::Quit);
- }
- let mut match_count = 0;
- for worker in workers {
- match_count += worker.join().unwrap();
+ paths_searched.fetch_add(1, Ordering::SeqCst);
+ outbuf.clear();
+ {
+ // This block actually executes the search and prints the
+ // results into outbuf.
+ let mut printer = args.printer(&mut outbuf);
+ let count =
+ if dent.is_stdin() {
+ worker.run(&mut printer, Work::Stdin)
+ } else {
+ worker.run(&mut printer, Work::DirEntry(dent))
+ };
+ match_count.fetch_add(count as usize, Ordering::SeqCst);
+ if quiet_matched.set_match(count > 0) {
+ return Quit;
+ }
+ }
+ if !outbuf.get_ref().is_empty() {
+ // This should be the only mutex in all of ripgrep. Since the
+ // common case is to report a small number of matches relative
+ // to the corpus, this really shouldn't matter much.
+ //
+ // Still, it'd be nice to send this on a channel, but then we'd
+ // need to manage a pool of outbufs, which would complicate the
+ // code.
+ let mut out = out.lock().unwrap();
+ out.write(&outbuf);
+ }
+ Continue
+ })
+ });
+ if !args.paths().is_empty() && paths_searched.load(Ordering::SeqCst) == 0 {
+ eprint_nothing_searched();
}
- Ok(match_count)
+ Ok(match_count.load(Ordering::SeqCst) as u64)
}
fn run_one_thread(args: Arc<Args>) -> Result<u64> {
- let mut worker = Worker {
- args: args.clone(),
- inpbuf: args.input_buffer(),
- grep: args.grep(),
- match_count: 0,
- };
+ let mut worker = args.worker();
let mut term = args.stdout();
let mut paths_searched: u64 = 0;
- for dent in args.walker() {
+ let mut match_count = 0;
+ for result in args.walker() {
+ let dent = match get_or_log_dir_entry(result) {
+ None => continue,
+ Some(dent) => dent,
+ };
let mut printer = args.printer(&mut term);
- if worker.match_count > 0 {
+ if match_count > 0 {
if args.quiet() {
break;
}
@@ -179,32 +182,53 @@ fn run_one_thread(args: Arc<Args>) -> Result<u64> {
}
}
paths_searched += 1;
- if dent.is_stdin() {
- worker.do_work(&mut printer, WorkReady::Stdin);
- } else {
- let file = match File::open(dent.path()) {
- Ok(file) => file,
- Err(err) => {
- eprintln!("{}: {}", dent.path().display(), err);
- continue;
- }
+ match_count +=
+ if dent.is_stdin() {
+ worker.run(&mut printer, Work::Stdin)
+ } else {
+ worker.run(&mut printer, Work::DirEntry(dent))
};
- worker.do_work(&mut printer, WorkReady::DirFile(dent, file));
- }
}
if !args.paths().is_empty() && paths_searched == 0 {
- eprintln!("No files were searched, which means ripgrep probably \
- applied a filter you didn't expect. \
- Try running again with --debug.");
+ eprint_nothing_searched();
}
- Ok(worker.match_count)
+ Ok(match_count)
+}
+
+fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
+ let print_args = args.clone();
+ let (tx, rx) = mpsc::channel::<ignore::DirEntry>();
+ let print_thread = thread::spawn(move || {
+ let term = print_args.stdout();
+ let mut printer = print_args.printer(term);
+ let mut file_count = 0;
+ for dent in rx.iter() {
+ printer.path(dent.path());
+ file_count += 1;
+ }
+ file_count
+ });
+ args.walker_parallel().run(move || {
+ let tx = tx.clone();
+ Box::new(move |result| {
+ if let Some(dent) = get_or_log_dir_entry(result) {
+ tx.send(dent).unwrap();
+ }
+ ignore::WalkState::Continue
+ })
+ });
+ Ok(print_thread.join().unwrap())
}
-fn run_files(args: Arc<Args>) -> Result<u64> {
+fn run_files_one_thread(args: Arc<Args>) -> Result<u64> {
let term = args.stdout();
let mut printer = args.printer(term);
let mut file_count = 0;
- for dent in args.walker() {
+ for result in args.walker() {
+ let dent = match get_or_log_dir_entry(result) {
+ None => continue,
+ Some(dent) => dent,
+ };
printer.path(dent.path());
file_count += 1;
}
@@ -222,163 +246,64 @@ fn run_types(args: Arc<Args>) -> Result<u64> {
Ok(ty_count)
}
-enum Work {
- Stdin,
- File(DirEntry),
- Quit,
-}
-
-enum WorkReady {
- Stdin,
- DirFile(DirEntry, File),
-}
-
-struct MultiWorker {
- chan_work: Stealer<Work>,
- quiet_matched: QuietMatched,
- out: Arc<Mutex<Out>>,
- #[cfg(not(windows))]
- outbuf: Option<ColoredTerminal<term::TerminfoTerminal<Vec<u8>>>>,
- #[cfg(windows)]
- outbuf: Option<ColoredTerminal<WindowsBuffer>>,
- worker: Worker,
-}
-
-struct Worker {
- args: Arc<Args>,
- inpbuf: InputBuffer,
- grep: Grep,
- match_count: u64,
-}
-
-impl MultiWorker {
- fn run(mut self) -> u64 {
- loop {
- if self.quiet_matched.has_match() {
- break;
- }
- let work = match self.chan_work.steal() {
- Stolen::Empty | Stolen::Abort => continue,
- Stolen::Data(Work::Quit) => break,
- Stolen::Data(Work::Stdin) => WorkReady::Stdin,
- Stolen::Data(Work::File(ent)) => {
- match File::open(ent.path()) {
- Ok(file) => WorkReady::DirFile(ent, file),
- Err(err) => {
- eprintln!("{}: {}", ent.path().display(), err);
- continue;
- }
- }
- }
- };
- let mut outbuf = self.outbuf.take().unwrap();
- outbuf.clear();
- let mut printer = self.worker.args.printer(outbuf);
- self.worker.do_work(&mut printer, work);
- if self.quiet_matched.set_match(self.worker.match_count > 0) {
- break;
- }
- let outbuf = printer.into_inner();
- if !outbuf.get_ref().is_empty() {
- let mut out = self.out.lock().unwrap();
- out.write(&outbuf);
- }
- self.outbuf = Some(outbuf);
+fn get_or_log_dir_entry(
+ result: result::Result<ignore::DirEntry, ignore::Error>,
+) -> Option<ignore::DirEntry> {
+ match result {
+ Err(err) => {
+ eprintln!("{}", err);
+ None
}
- self.worker.match_count
- }
-}
-
-impl Worker {
- fn do_work<W: Terminal + Send>(
- &mut self,
- printer: &mut Printer<W>,
- work: WorkReady,
- ) {
- let result = match work {
- WorkReady::Stdin => {
- let stdin = io::stdin();
- let stdin = stdin.lock();
- self.search(printer, &Path::new("<stdin>"), stdin)
- }
- WorkReady::DirFile(ent, file) => {
- let mut path = ent.path();
- if let Some(p) = strip_prefix("./", path) {
- path = p;
- }
- if self.args.mmap() {
- self.search_mmap(printer, path, &file)
- } else {
- self.search(printer, path, file)
- }
- }
- };
- match result {
- Ok(count) => {
- self.match_count += count;
- }
- Err(err) => {
+ Ok(dent) => {
+ if let Some(err) = dent.error() {
eprintln!("{}", err);
}
+ if !dent.file_type().map_or(true, |x| x.is_file()) {
+ None
+ } else {
+ Some(dent)
+ }
}
}
+}
- fn search<R: io::Read, W: Terminal + Send>(
- &mut self,
- printer: &mut Printer<W>,
- path: &Path,
- rdr: R,
- ) -> Result<u64> {
- self.args.searcher(
- &mut self.inpbuf,
- printer,
- &self.grep,
- path,
- rdr,
- ).run().map_err(From::from)
- }
-
- fn search_mmap<W: Terminal + Send>(
- &mut self,
- printer: &mut Printer<W>,
- path: &Path,
- file: &File,
- ) -> Result<u64> {
- if try!(file.metadata()).len() == 0 {
- // Opening a memory map with an empty file results in an error.
- // However, this may not actually be an empty file! For example,
- // /proc/cpuinfo reports itself as an empty file, but it can
- // produce data when it's read from. Therefore, we fall back to
- // regular read calls.
- return self.search(printer, path, file);
- }
- let mmap = try!(Mmap::open(file, Protection::Read));
- Ok(self.args.searcher_buffer(
- printer,
- &self.grep,
- path,
- unsafe { mmap.as_slice() },
- ).run())
- }
+fn eprint_nothing_searched() {
+ eprintln!("No files were searched, which means ripgrep probably \
+ applied a filter you didn't expect. \
+ Try running again with --debug.");
}
+/// A simple thread safe abstraction for determining whether a search should
+/// stop if the user has requested quiet mode.
#[derive(Clone, Debug)]
-struct QuietMatched(Arc<Option<AtomicBool>>);
+pub struct QuietMatched(Arc<Option<AtomicBool>>);
impl QuietMatched {
- fn new(quiet: bool) -> QuietMatched {
+ /// Create a new QuietMatched value.
+ ///
+ /// If quiet is true, then set_match and has_match will reflect whether
+ /// a search should quit or not because it found a match.
+ ///
+ /// If quiet is false, then set_match is always a no-op and has_match
+ /// always returns false.
+ pub fn new(quiet: bool) -> QuietMatched {
let atomic = if quiet { Some(AtomicBool::new(false)) } else { None };
QuietMatched(Arc::new(atomic))
}
- fn has_match(&self) -> bool {
+ /// Returns true if and only if quiet mode is enabled and a match has
+ /// occurred.
+ pub fn has_match(&self) -> bool {
match *self.0 {
None => false,
Some(ref matched) => matched.load(Ordering::SeqCst),
}
}
- fn set_match(&self, yes: bool) -> bool {
+ /// Sets whether a match has occurred or not.
+ ///
+ /// If quiet mode is disabled, then this is a no-op.
+ pub fn set_match(&self, yes: bool) -> bool {
match *self.0 {
None => false,
Some(_) if !yes => false,
diff --git a/src/printer.rs b/src/printer.rs
index e7373bce..1b8e5965 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -158,6 +158,7 @@ impl<W: Terminal + Send> Printer<W> {
}
/// Flushes the underlying writer and returns it.
+ #[allow(dead_code)]
pub fn into_inner(mut self) -> W {
let _ = self.wtr.flush();
self.wtr
diff --git a/src/worker.rs b/src/worker.rs
new file mode 100644
index 00000000..797fe9d7
--- /dev/null
+++ b/src/worker.rs
@@ -0,0 +1,253 @@
+use std::fs::File;
+use std::io;
+use std::path::Path;
+
+use grep::Grep;
+use ignore::DirEntry;
+use memmap::{Mmap, Protection};
+use term::Terminal;
+
+use pathutil::strip_prefix;
+use printer::Printer;
+use search_buffer::BufferSearcher;
+use search_stream::{InputBuffer, Searcher};
+
+use Result;
+
+pub enum Work {
+ Stdin,
+ DirEntry(DirEntry),
+}
+
+pub struct WorkerBuilder {
+ grep: Grep,
+ opts: Options,
+}
+
+#[derive(Clone, Debug)]
+struct Options {
+ mmap: bool,
+ after_context: usize,
+ before_context: usize,
+ count: bool,
+ files_with_matches: bool,
+ eol: u8,
+ invert_match: bool,
+ line_number: bool,
+ quiet: bool,
+ text: bool,
+}
+
+impl Default for Options {
+ fn default() -> Options {
+ Options {
+ mmap: false,
+ after_context: 0,
+ before_context: 0,
+ count: false,
+ files_with_matches: false,
+ eol: b'\n',
+ invert_match: false,
+ line_number: false,
+ quiet: false,
+ text: false,
+ }
+ }
+}
+
+impl WorkerBuilder {
+ /// Create a new builder for a worker.
+ ///
+ /// A reusable input buffer and a grep matcher are required, but there
+ /// are numerous additional options that can be configured on this builder.
+ pub fn new(grep: Grep) -> WorkerBuilder {
+ WorkerBuilder {
+ grep: grep,
+ opts: Options::default(),
+ }
+ }
+
+ /// Create the worker from this builder.
+ pub fn build(self) -> Worker {
+ let mut inpbuf = InputBuffer::new();
+ inpbuf.eol(self.opts.eol);
+ Worker {
+ grep: self.grep,
+ inpbuf: inpbuf,
+ opts: self.opts,
+ }
+ }
+
+ /// The number of contextual lines to show after each match. The default
+ /// is zero.
+ pub fn after_context(mut self, count: usize) -> Self {
+ self.opts.after_context = count;
+ self
+ }
+
+ /// The number of contextual lines to show before each match. The default
+ /// is zero.
+ pub fn before_context(mut self, count: usize) -> Self {
+ self.opts.before_context = count;
+ self
+ }
+
+ /// If enabled, searching will print a count instead of each match.
+ ///
+ /// Disabled by default.
+ pub fn count(mut self, yes: bool) -> Self {
+ self.opts.count = yes;
+ self
+ }
+
+ /// If enabled, searching will print the path instead of each match.
+ ///
+ /// Disabled by default.
+ pub fn files_with_matches(mut self, yes: bool) -> Self {
+ self.opts.files_with_matches = yes;
+ self
+ }
+
+ /// Set the end-of-line byte used by this searcher.
+ pub fn eol(mut self, eol: u8) -> Self {
+ self.opts.eol = eol;
+ self
+ }
+
+ /// If enabled, matching is inverted so that lines that *don't* match the
+ /// given pattern are treated as matches.
+ pub fn invert_match(mut self, yes: bool) -> Self {
+ self.opts.invert_match = yes;
+ self
+ }
+
+ /// If enabled, compute line numbers and prefix each line of output with
+ /// them.
+ pub fn line_number(mut self, yes: bool) -> Self {
+ self.opts.line_number = yes;
+ self
+ }
+
+ /// If enabled, try to use memory maps for searching if possible.
+ pub fn mmap(mut self, yes: bool) -> Self {
+ self.opts.mmap = 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 {
+ self.opts.quiet = yes;
+ self
+ }
+
+ /// If enabled, search binary files as if they were text.
+ pub fn text(mut self, yes: bool) -> Self {
+ self.opts.text = yes;
+ self
+ }
+}
+
+/// Worker is responsible for executing searches on file paths, while choosing
+/// streaming search or memory map search as appropriate.
+pub struct Worker {
+ inpbuf: InputBuffer,
+ grep: Grep,
+ opts: Options,
+}
+
+impl Worker {
+ /// Execute the worker with the given printer and work item.
+ ///
+ /// A work item can either be stdin or a file path.
+ pub fn run<W: Terminal + Send>(
+ &mut self,
+ printer: &mut Printer<W>,
+ work: Work,
+ ) -> u64 {
+ let result = match work {
+ Work::Stdin => {
+ let stdin = io::stdin();
+ let stdin = stdin.lock();
+ self.search(printer, &Path::new("<stdin>"), stdin)
+ }
+ Work::DirEntry(dent) => {
+ let mut path = dent.path();
+ let file = match File::open(path) {
+ Ok(file) => file,
+ Err(err) => {
+ eprintln!("{}: {}", path.display(), err);
+ return 0;
+ }
+ };
+ if let Some(p) = strip_prefix("./", path) {
+ path = p;
+ }
+ if self.opts.mmap {
+ self.search_mmap(printer, path, &file)
+ } else {
+ self.search(printer, path, file)
+ }
+ }
+ };
+ match result {
+ Ok(count) => {
+ count
+ }
+ Err(err) => {
+ eprintln!("{}", err);
+ 0
+ }
+ }
+ }
+
+ fn search<R: io::Read, W: Terminal + Send>(
+ &mut self,
+ printer: &mut Printer<W>,
+ path: &Path,
+ rdr: R,
+ ) -> Result<u64> {
+ let searcher = Searcher::new(
+ &mut self.inpbuf, printer, &self.grep, path, rdr);
+ searcher
+ .after_context(self.opts.after_context)
+ .before_context(self.opts.before_context)
+ .count(self.opts.count)
+ .files_with_matches(self.opts.files_with_matches)
+ .eol(self.opts.eol)
+ .line_number(self.opts.line_number)
+ .invert_match(self.opts.invert_match)
+ .quiet(self.opts.quiet)
+ .text(self.opts.text)
+ .run()
+ .map_err(From::from)
+ }
+
+ fn search_mmap<W: Terminal + Send>(
+ &mut self,
+ printer: &mut Printer<W>,
+ path: &Path,
+ file: &File,
+ ) -> Result<u64> {
+ if try!(file.metadata()).len() == 0 {
+ // Opening a memory map with an empty file results in an error.
+ // However, this may not actually be an empty file! For example,
+ // /proc/cpuinfo reports itself as an empty file, but it can
+ // produce data when it's read from. Therefore, we fall back to
+ // regular read calls.
+ return self.search(printer, path, file);
+ }
+ let mmap = try!(Mmap::open(file, Protection::Read));
+ let searcher = BufferSearcher::new(
+ printer, &self.grep, path, unsafe { mmap.as_slice() });
+ Ok(searcher
+ .count(self.opts.count)
+ .files_with_matches(self.opts.files_with_matches)
+ .eol(self.opts.eol)
+ .line_number(self.opts.line_number)
+ .invert_match(self.opts.invert_match)
+ .quiet(self.opts.quiet)
+ .text(self.opts.text)
+ .run())
+ }
+}