mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-06-30 22:23:44 +02:00
Lots of progress:
- Refactored interaction between CLI args and rest of xrep. - Filling in a lot more options, including file type filtering. - Fixing some bugs in globbing/ignoring. - More documentation.
This commit is contained in:
286
src/main.rs
286
src/main.rs
@ -19,7 +19,6 @@ extern crate rustc_serialize;
|
||||
extern crate thread_local;
|
||||
extern crate walkdir;
|
||||
|
||||
use std::cmp;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
@ -30,14 +29,13 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use crossbeam::sync::chase_lev::{self, Steal, Stealer};
|
||||
use docopt::Docopt;
|
||||
use grep::{Grep, GrepBuilder};
|
||||
use grep::Grep;
|
||||
use parking_lot::Mutex;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use ignore::Ignore;
|
||||
use args::Args;
|
||||
use out::Out;
|
||||
use printer::Printer;
|
||||
use search::{InputBuffer, Searcher};
|
||||
use search::InputBuffer;
|
||||
|
||||
macro_rules! errored {
|
||||
($($tt:tt)*) => {
|
||||
@ -52,64 +50,22 @@ macro_rules! eprintln {
|
||||
}}
|
||||
}
|
||||
|
||||
mod args;
|
||||
mod gitignore;
|
||||
mod glob;
|
||||
mod ignore;
|
||||
mod out;
|
||||
mod printer;
|
||||
mod search;
|
||||
mod types;
|
||||
mod walk;
|
||||
|
||||
const USAGE: &'static str = "
|
||||
Usage: xrep [options] <pattern> [<path> ...]
|
||||
xrep --files [<path> ...]
|
||||
|
||||
xrep is like the silver searcher and grep, but faster than both.
|
||||
|
||||
WARNING: Searching stdin isn't yet supported.
|
||||
|
||||
Options:
|
||||
-c, --count Suppress normal output and show count of line
|
||||
matches.
|
||||
-A, --after-context NUM Show NUM lines after each match.
|
||||
-B, --before-context NUM Show NUM lines before each match.
|
||||
-C, --context NUM Show NUM lines before and after each match.
|
||||
--debug Show debug messages.
|
||||
--files Print each file that would be searched
|
||||
(but don't search).
|
||||
--hidden Search hidden directories and files.
|
||||
-i, --ignore-case Case insensitive search.
|
||||
-L, --follow Follow symlinks.
|
||||
-n, --line-number Show line numbers (1-based).
|
||||
-t, --threads ARG The number of threads to use. Defaults to the
|
||||
number of logical CPUs. [default: 0]
|
||||
-v, --invert-match Invert matching.
|
||||
";
|
||||
|
||||
#[derive(RustcDecodable)]
|
||||
struct Args {
|
||||
arg_pattern: String,
|
||||
arg_path: Vec<String>,
|
||||
flag_after_context: usize,
|
||||
flag_before_context: usize,
|
||||
flag_context: usize,
|
||||
flag_count: bool,
|
||||
flag_debug: bool,
|
||||
flag_files: bool,
|
||||
flag_follow: bool,
|
||||
flag_hidden: bool,
|
||||
flag_ignore_case: bool,
|
||||
flag_invert_match: bool,
|
||||
flag_line_number: bool,
|
||||
flag_threads: usize,
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
|
||||
|
||||
fn main() {
|
||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
match run(args) {
|
||||
Ok(_) => process::exit(0),
|
||||
match Args::parse().and_then(run) {
|
||||
Ok(count) if count == 0 => process::exit(1),
|
||||
Ok(count) => process::exit(0),
|
||||
Err(err) => {
|
||||
let _ = writeln!(&mut io::stderr(), "{}", err);
|
||||
process::exit(1);
|
||||
@ -117,194 +73,158 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
fn run(mut args: Args) -> Result<()> {
|
||||
let mut logb = env_logger::LogBuilder::new();
|
||||
if args.flag_debug {
|
||||
logb.filter(None, log::LogLevelFilter::Debug);
|
||||
} else {
|
||||
logb.filter(None, log::LogLevelFilter::Warn);
|
||||
}
|
||||
if let Err(err) = logb.init() {
|
||||
errored!("failed to initialize logger: {}", err);
|
||||
}
|
||||
|
||||
if args.arg_path.is_empty() {
|
||||
args.arg_path.push("./".to_string());
|
||||
}
|
||||
if args.arg_path.iter().any(|p| p == "-") {
|
||||
errored!("searching <stdin> isn't yet supported");
|
||||
}
|
||||
if args.flag_files {
|
||||
fn run(args: Args) -> Result<u64> {
|
||||
if args.files() {
|
||||
return run_files(args);
|
||||
}
|
||||
if args.type_list() {
|
||||
return run_types(args);
|
||||
}
|
||||
let args = Arc::new(args);
|
||||
let out = Arc::new(Mutex::new(args.out(io::stdout())));
|
||||
let mut workers = vec![];
|
||||
let out = Arc::new(Mutex::new(Out::new(args.clone(), io::stdout())));
|
||||
|
||||
let mut chan_work_send = {
|
||||
let (worker, stealer) = chase_lev::deque();
|
||||
for _ in 0..args.num_workers() {
|
||||
let grepb =
|
||||
GrepBuilder::new(&args.arg_pattern)
|
||||
.case_insensitive(args.flag_ignore_case);
|
||||
let mut workq = {
|
||||
let (workq, stealer) = chase_lev::deque();
|
||||
for _ in 0..args.threads() {
|
||||
let worker = Worker {
|
||||
args: args.clone(),
|
||||
out: out.clone(),
|
||||
chan_work: stealer.clone(),
|
||||
inpbuf: InputBuffer::new(),
|
||||
inpbuf: args.input_buffer(),
|
||||
outbuf: Some(vec![]),
|
||||
grep: try!(grepb.build()),
|
||||
grep: try!(args.grep()),
|
||||
};
|
||||
workers.push(thread::spawn(move || worker.run()));
|
||||
}
|
||||
worker
|
||||
workq
|
||||
};
|
||||
|
||||
for p in &args.arg_path {
|
||||
for path in args.walker(p) {
|
||||
chan_work_send.push(Message::Some(path));
|
||||
for p in args.paths() {
|
||||
if p == Path::new("-") {
|
||||
workq.push(Work::Stdin)
|
||||
} else {
|
||||
for path in args.walker(p) {
|
||||
workq.push(Work::File(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
for _ in 0..workers.len() {
|
||||
chan_work_send.push(Message::Quit);
|
||||
workq.push(Work::Quit);
|
||||
}
|
||||
let mut match_count = 0;
|
||||
for worker in workers {
|
||||
worker.join().unwrap();
|
||||
match_count += worker.join().unwrap();
|
||||
}
|
||||
Ok(())
|
||||
Ok(match_count)
|
||||
}
|
||||
|
||||
fn run_files(args: Args) -> Result<()> {
|
||||
fn run_files(args: Args) -> Result<u64> {
|
||||
let mut printer = Printer::new(io::BufWriter::new(io::stdout()));
|
||||
for p in &args.arg_path {
|
||||
for path in args.walker(p) {
|
||||
printer.path(path);
|
||||
let mut file_count = 0;
|
||||
for p in args.paths() {
|
||||
if p == Path::new("-") {
|
||||
printer.path(&Path::new("<stdin>"));
|
||||
file_count += 1;
|
||||
} else {
|
||||
for path in args.walker(p) {
|
||||
printer.path(path);
|
||||
file_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(file_count)
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn printer<W: io::Write>(&self, wtr: W) -> Printer<W> {
|
||||
Printer::new(wtr)
|
||||
}
|
||||
|
||||
fn num_workers(&self) -> usize {
|
||||
let mut num = self.flag_threads;
|
||||
if num == 0 {
|
||||
num = cmp::min(8, num_cpus::get());
|
||||
}
|
||||
num
|
||||
}
|
||||
|
||||
fn walker<P: AsRef<Path>>(&self, path: P) -> walk::Iter {
|
||||
let wd = WalkDir::new(path).follow_links(self.flag_follow);
|
||||
let mut ig = Ignore::new();
|
||||
ig.ignore_hidden(!self.flag_hidden);
|
||||
walk::Iter::new(ig, wd)
|
||||
}
|
||||
|
||||
fn before_context(&self) -> usize {
|
||||
if self.flag_context > 0 {
|
||||
self.flag_context
|
||||
} else {
|
||||
self.flag_before_context
|
||||
}
|
||||
}
|
||||
|
||||
fn after_context(&self) -> usize {
|
||||
if self.flag_context > 0 {
|
||||
self.flag_context
|
||||
} else {
|
||||
self.flag_after_context
|
||||
}
|
||||
}
|
||||
|
||||
fn has_context(&self) -> bool {
|
||||
self.before_context() > 0 || self.after_context() > 0
|
||||
fn run_types(args: Args) -> Result<u64> {
|
||||
let mut printer = Printer::new(io::BufWriter::new(io::stdout()));
|
||||
let mut ty_count = 0;
|
||||
for def in args.type_defs() {
|
||||
printer.type_def(def);
|
||||
ty_count += 1;
|
||||
}
|
||||
Ok(ty_count)
|
||||
}
|
||||
|
||||
enum Message<T> {
|
||||
Some(T),
|
||||
enum Work {
|
||||
File(PathBuf),
|
||||
Stdin,
|
||||
Quit,
|
||||
}
|
||||
|
||||
struct Worker {
|
||||
args: Arc<Args>,
|
||||
out: Arc<Mutex<Out<io::Stdout>>>,
|
||||
chan_work: Stealer<Message<PathBuf>>,
|
||||
chan_work: Stealer<Work>,
|
||||
inpbuf: InputBuffer,
|
||||
outbuf: Option<Vec<u8>>,
|
||||
grep: Grep,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
fn run(mut self) {
|
||||
fn run(mut self) -> u64 {
|
||||
let mut match_count = 0;
|
||||
loop {
|
||||
let path = match self.chan_work.steal() {
|
||||
let (path, file) = match self.chan_work.steal() {
|
||||
Steal::Empty | Steal::Abort => continue,
|
||||
Steal::Data(Message::Quit) => break,
|
||||
Steal::Data(Message::Some(path)) => path,
|
||||
};
|
||||
let file = match File::open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
eprintln!("{}: {}", path.display(), err);
|
||||
continue;
|
||||
Steal::Data(Work::Quit) => break,
|
||||
Steal::Data(Work::File(path)) => {
|
||||
match File::open(&path) {
|
||||
Ok(file) => (path, Some(file)),
|
||||
Err(err) => {
|
||||
eprintln!("{}: {}", path.display(), err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Steal::Data(Work::Stdin) => {
|
||||
(Path::new("<stdin>").to_path_buf(), None)
|
||||
}
|
||||
};
|
||||
let mut outbuf = self.outbuf.take().unwrap();
|
||||
outbuf.clear();
|
||||
let mut printer = self.args.printer(outbuf);
|
||||
{
|
||||
let mut searcher = Searcher::new(
|
||||
&mut self.inpbuf,
|
||||
&mut printer,
|
||||
&self.grep,
|
||||
&path,
|
||||
file,
|
||||
);
|
||||
searcher = searcher.count(self.args.flag_count);
|
||||
searcher = searcher.line_number(self.args.flag_line_number);
|
||||
searcher = searcher.invert_match(self.args.flag_invert_match);
|
||||
searcher = searcher.after_context(self.args.after_context());
|
||||
searcher = searcher.before_context(self.args.before_context());
|
||||
if let Err(err) = searcher.run() {
|
||||
eprintln!("{}", err);
|
||||
let result = match file {
|
||||
None => {
|
||||
let stdin = io::stdin();
|
||||
let stdin = stdin.lock();
|
||||
self.search(&mut printer, &path, stdin)
|
||||
}
|
||||
Some(file) => {
|
||||
self.search(&mut printer, &path, file)
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Ok(count) => {
|
||||
match_count += count;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
let outbuf = printer.into_inner();
|
||||
if !outbuf.is_empty() {
|
||||
let mut out = self.out.lock();
|
||||
out.write_file_matches(&outbuf);
|
||||
out.write(&outbuf);
|
||||
}
|
||||
self.outbuf = Some(outbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Out<W: io::Write> {
|
||||
args: Arc<Args>,
|
||||
wtr: io::BufWriter<W>,
|
||||
printed: bool,
|
||||
}
|
||||
|
||||
impl<W: io::Write> Out<W> {
|
||||
fn new(args: Arc<Args>, wtr: W) -> Out<W> {
|
||||
Out {
|
||||
args: args,
|
||||
wtr: io::BufWriter::new(wtr),
|
||||
printed: false,
|
||||
}
|
||||
match_count
|
||||
}
|
||||
|
||||
fn write_file_matches(&mut self, buf: &[u8]) {
|
||||
if self.printed && self.args.has_context() {
|
||||
let _ = self.wtr.write_all(b"--\n");
|
||||
}
|
||||
let _ = self.wtr.write_all(buf);
|
||||
let _ = self.wtr.flush();
|
||||
self.printed = true;
|
||||
fn search<R: io::Read, W: io::Write>(
|
||||
&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)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user