diff --git a/src/args.rs b/src/args.rs index 521dda84..cb858f80 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,6 +1,7 @@ use std::cmp; use std::env; -use std::io; +use std::fs; +use std::io::{self, BufRead}; use std::path::{Path, PathBuf}; use std::process; @@ -34,6 +35,7 @@ use Result; /// (TL;DR: The CLI parser is generated from the usage string below.) const USAGE: &'static str = " Usage: rg [options] -e PATTERN ... [ ...] + rg [options] -f FILE [ ...] rg [options] [ ...] rg [options] --files [ ...] rg [options] --type-list @@ -107,6 +109,11 @@ Less common options: --debug Show debug messages. + -f, --file FILE + Search for patterns specified in a file, one per line. Empty pattern + lines will match all input lines, and the newline is not counted as part + of the pattern. + --files Print each file that would be searched (but don't search). @@ -242,6 +249,7 @@ pub struct RawArgs { flag_count: bool, flag_files_with_matches: bool, flag_debug: bool, + flag_file: Option, flag_files: bool, flag_follow: bool, flag_glob: Vec, @@ -479,23 +487,32 @@ impl RawArgs { btypes.build().map_err(From::from) } - fn pattern(&self) -> String { - if !self.flag_regexp.is_empty() { - if self.flag_fixed_strings { - self.flag_regexp.iter().cloned().map(|lit| { - self.word_pattern(regex::quote(&lit)) - }).collect::>().join("|") + fn pattern(&self) -> Result { + let patterns: Vec = if !self.flag_regexp.is_empty() { + self.flag_regexp.iter().cloned().collect() + } else if let Some(ref file) = self.flag_file { + if file == "-" { + // We need two local variables here to get the lock + // lifetimes correct. + let stdin = io::stdin(); + let result = stdin.lock().lines().collect(); + try!(result) } else { - self.flag_regexp.iter().cloned().map(|pat| { - self.word_pattern(pat) - }).collect::>().join("|") + let f = try!(fs::File::open(&Path::new(file))); + try!(io::BufReader::new(f).lines().collect()) } } else { - if self.flag_fixed_strings { - self.word_pattern(regex::quote(&self.arg_pattern)) - } else { - self.word_pattern(self.arg_pattern.clone()) - } + vec![self.arg_pattern.clone()] + }; + + if self.flag_fixed_strings { + Ok(patterns.into_iter().map(|p| { + self.word_pattern(regex::quote(&p)) + }).collect::>().join("|")) + } else { + Ok(patterns.into_iter().map(|p| { + self.word_pattern(p) + }).collect::>().join("|")) } } @@ -520,7 +537,7 @@ impl RawArgs { let casei = self.flag_ignore_case && !self.flag_case_sensitive; - GrepBuilder::new(&self.pattern()) + GrepBuilder::new(&try!(self.pattern())) .case_smart(smart) .case_insensitive(casei) .line_terminator(self.eol()) diff --git a/tests/tests.rs b/tests/tests.rs index 2a53c479..3481121b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -902,6 +902,29 @@ clean!(regression_228, "test", ".", |wd: WorkDir, mut cmd: Command| { wd.assert_err(&mut cmd); }); +// See: https://github.com/BurntSushi/ripgrep/issues/7 +sherlock!(feature_7, "-fpat", "sherlock", |wd: WorkDir, mut cmd: Command| { + wd.create("pat", "Sherlock\nHolmes"); + let lines: String = wd.stdout(&mut cmd); + let expected = "\ +For the Doctor Watsons of this world, as opposed to the Sherlock +Holmeses, success in the province of detective work must always +be, to a very large extent, the result of luck. Sherlock Holmes +"; + assert_eq!(lines, expected); +}); + +// See: https://github.com/BurntSushi/ripgrep/issues/7 +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 +"; + assert_eq!(lines, expected); +}); + // See: https://github.com/BurntSushi/ripgrep/issues/20 sherlock!(feature_20_no_filename, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { diff --git a/tests/workdir.rs b/tests/workdir.rs index 5ef3b72e..d9de0f8a 100644 --- a/tests/workdir.rs +++ b/tests/workdir.rs @@ -153,7 +153,41 @@ impl WorkDir { /// Gets the output of a command. If the command failed, then this panics. pub fn output(&self, cmd: &mut process::Command) -> process::Output { - let o = cmd.output().unwrap(); + let output = cmd.output().unwrap(); + self.expect_success(cmd, output) + } + + /// Pipe `input` to a command, and collect the output. + pub fn pipe( + &self, + cmd: &mut process::Command, + input: &str + ) -> process::Output { + cmd.stdin(process::Stdio::piped()); + cmd.stdout(process::Stdio::piped()); + cmd.stderr(process::Stdio::piped()); + + let mut child = cmd.spawn().unwrap(); + + // Pipe input to child process using a separate thread to avoid + // risk of deadlock between parent and child process. + let mut stdin = child.stdin.take().expect("expected standard input"); + let input = input.to_owned(); + let worker = thread::spawn(move || { + write!(stdin, "{}", input) + }); + + let output = self.expect_success(cmd, child.wait_with_output().unwrap()); + worker.join().unwrap().unwrap(); + output + } + + /// If `o` is not the output of a successful process run + fn expect_success( + &self, + cmd: &process::Command, + o: process::Output + ) -> process::Output { if !o.status.success() { let suggest = if o.stderr.is_empty() {