diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 570 |
1 files changed, 212 insertions, 358 deletions
diff --git a/src/main.rs b/src/main.rs index 41e4dc19..772cb106 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,13 @@ -#![allow(dead_code, unused_imports, unused_mut, unused_variables)] - extern crate atty; -extern crate bytecount; #[macro_use] extern crate clap; -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; -extern crate libc; #[macro_use] extern crate log; -extern crate memchr; -extern crate memmap; extern crate num_cpus; extern crate regex; extern crate same_file; @@ -24,416 +15,279 @@ extern crate termcolor; #[cfg(windows)] extern crate winapi; -use std::error::Error; +use std::io; use std::process; -use std::result; -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::mpsc; -use std::thread; -use std::time::{Duration, Instant}; +use std::sync::{Arc, Mutex}; +use std::time::Instant; -use args::Args; -use worker::Work; +use ignore::WalkState; -macro_rules! errored { - ($($tt:tt)*) => { - return Err(From::from(format!($($tt)*))); - } -} +use args::Args; +use subject::Subject; #[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>>; +pub type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>; -fn main() { - 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); - // } - // } +pub fn main() { + match Args::parse().and_then(run) { + Ok(true) => process::exit(0), + Ok(false) => process::exit(1), + Err(err) => { + eprintln!("{}", err); + process::exit(2); + } + } } -fn run(args: Arc<Args>) -> Result<u64> { - if args.never_match() { - return Ok(0); +fn run(args: Args) -> Result<bool> { + use args::Command::*; + + match args.command()? { + Search => search(args), + SearchParallel => search_parallel(args), + SearchNever => Ok(false), + Files => files(args), + FilesParallel => files_parallel(args), + Types => types(args), } - let threads = args.threads(); - if args.files() { - if threads == 1 || args.is_one_path() { - run_files_one_thread(&args) - } else { - run_files_parallel(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(); } - } else if args.type_list() { - run_types(&args) - } else if threads == 1 || args.is_one_path() { - run_one_thread(&args) - } else { - run_parallel(&args) + 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) } -fn run_parallel(args: &Arc<Args>) -> Result<u64> { - let start_time = Instant::now(); - let bufwtr = Arc::new(args.buffer_writer()); - let quiet_matched = args.quiet_matched(); - let paths_searched = Arc::new(AtomicUsize::new(0)); - let match_line_count = Arc::new(AtomicUsize::new(0)); - let paths_matched = Arc::new(AtomicUsize::new(0)); +/// 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; - args.walker_parallel().run(|| { - let args = Arc::clone(args); - let quiet_matched = quiet_matched.clone(); - let paths_searched = paths_searched.clone(); - let match_line_count = match_line_count.clone(); - let paths_matched = paths_matched.clone(); + 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 mut buf = bufwtr.buffer(); - let mut worker = args.worker(); - Box::new(move |result| { - use ignore::WalkState::*; - - if quiet_matched.has_match() { - return Quit; + 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 + }); } - let dent = match get_or_log_dir_entry( - result, - args.stdout_handle(), - args.files(), - ) { - None => return Continue, - Some(dent) => dent, + }; + + Box::new(move |result| { + let subject = match subject_builder.build_from_result(result) { + Some(subject) => subject, + None => return WalkState::Continue, }; - paths_searched.fetch_add(1, Ordering::SeqCst); - buf.clear(); - { - // This block actually executes the search and prints the - // results into outbuf. - let mut printer = args.printer(&mut buf); - let count = - if dent.is_stdin() { - worker.run(&mut printer, Work::Stdin) - } else { - worker.run(&mut printer, Work::DirEntry(dent)) - }; - match_line_count.fetch_add(count as usize, Ordering::SeqCst); - if quiet_matched.set_match(count > 0) { - return Quit; + 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 args.stats() && count > 0 { - paths_matched.fetch_add(1, Ordering::SeqCst); + }; + 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 } - // BUG(burntsushi): We should handle this error instead of ignoring - // it. See: https://github.com/BurntSushi/ripgrep/issues/200 - let _ = bufwtr.print(&buf); - Continue }) }); - if paths_searched.load(Ordering::SeqCst) == 0 { - eprint_nothing_searched(); + if let Some(err) = searcher_err.take() { + return Err(err); } - 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; - if args.stats() { - print_stats( - match_line_count, - paths_searched, - paths_matched, - start_time.elapsed(), - ); + 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(match_line_count) + Ok(matched.load(SeqCst)) } -fn run_one_thread(args: &Arc<Args>) -> Result<u64> { - let start_time = Instant::now(); - let mut stdout = args.stdout(); - let mut worker = args.worker(); - let mut paths_searched: u64 = 0; - let mut match_line_count = 0; - let mut paths_matched: u64 = 0; - for result in args.walker() { - let dent = match get_or_log_dir_entry( - result, - args.stdout_handle(), - args.files(), - ) { +/// 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, - Some(dent) => dent, }; - let mut printer = args.printer(&mut stdout); - if match_line_count > 0 { - if args.quiet() { + 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; } - if let Some(sep) = args.file_separator() { - printer = printer.file_separator(sep); - } - } - paths_searched += 1; - let count = - if dent.is_stdin() { - worker.run(&mut printer, Work::Stdin) - } else { - worker.run(&mut printer, Work::DirEntry(dent)) - }; - match_line_count += count; - if args.stats() && count > 0 { - paths_matched += 1; + // Otherwise, we have some other error that's preventing us from + // writing to stdout, so we should bubble it up. + return Err(err.into()); } } - if paths_searched == 0 { - eprint_nothing_searched(); - } - if args.stats() { - print_stats( - match_line_count, - paths_searched, - paths_matched, - start_time.elapsed(), - ); - } - Ok(match_line_count) + Ok(matched) } -fn run_files_parallel(args: Arc<Args>) -> Result<u64> { - let print_args = Arc::clone(&args); - let (tx, rx) = mpsc::channel::<ignore::DirEntry>(); - let print_thread = thread::spawn(move || { - let mut printer = print_args.printer(print_args.stdout()); - let mut file_count = 0; - for dent in rx.iter() { - if !print_args.quiet() { - printer.path(dent.path()); - } - file_count += 1; +/// 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())?; } - file_count + Ok(()) }); - args.walker_parallel().run(move || { - let args = Arc::clone(&args); + args.walker_parallel()?.run(|| { + let subject_builder = Arc::clone(&subject_builder); + let matched = Arc::clone(&matched); let tx = tx.clone(); + Box::new(move |result| { - if let Some(dent) = get_or_log_dir_entry( - result, - args.stdout_handle(), - args.files(), - ) { - tx.send(dent).unwrap(); - if args.quiet() { - return ignore::WalkState::Quit + 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, } } - ignore::WalkState::Continue }) }); - Ok(print_thread.join().unwrap()) -} - -fn run_files_one_thread(args: &Arc<Args>) -> Result<u64> { - let mut printer = args.printer(args.stdout()); - let mut file_count = 0; - for result in args.walker() { - let dent = match get_or_log_dir_entry( - result, - args.stdout_handle(), - args.files(), - ) { - None => continue, - Some(dent) => dent, - }; - file_count += 1; - if args.quiet() { - break; - } else { - printer.path(dent.path()); + 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(file_count) + Ok(matched.load(SeqCst)) } -fn run_types(args: &Arc<Args>) -> Result<u64> { - let mut printer = args.printer(args.stdout()); - let mut ty_count = 0; - for def in args.type_defs() { - printer.type_def(def); - ty_count += 1; - } - Ok(ty_count) -} +/// 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": ")?; -fn get_or_log_dir_entry( - result: result::Result<ignore::DirEntry, ignore::Error>, - stdout_handle: Option<&same_file::Handle>, - files_only: bool, -) -> Option<ignore::DirEntry> { - match result { - Err(err) => { - message!("{}", err); - None - } - Ok(dent) => { - if let Some(err) = dent.error() { - ignore_message!("{}", err); + let mut first = true; + for glob in def.globs() { + if !first { + stdout.write_all(b", ")?; } - if dent.file_type().is_none() { - return Some(dent); // entry is stdin - } - // A depth of 0 means the user gave the path explicitly, so we - // should always try to search it. - if dent.depth() == 0 && !ignore_entry_is_dir(&dent) { - return Some(dent); - } else if !ignore_entry_is_file(&dent) { - return None; - } - // If we are redirecting stdout to a file, then don't search that - // file. - if !files_only && is_stdout_file(&dent, stdout_handle) { - return None; - } - Some(dent) - } - } -} - -/// Returns true if and only if the given `ignore::DirEntry` 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 ignore_entry_is_dir(dent: &ignore::DirEntry) -> bool { - use std::os::windows::fs::MetadataExt; - use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; - - dent.metadata().map(|md| { - md.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 - }).unwrap_or(false) -} - -/// Returns true if and only if the given `ignore::DirEntry` points to a -/// directory. -#[cfg(not(windows))] -fn ignore_entry_is_dir(dent: &ignore::DirEntry) -> bool { - dent.file_type().map_or(false, |ft| ft.is_dir()) -} - -/// Returns true if and only if the given `ignore::DirEntry` 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 ignore_entry_is_file(dent: &ignore::DirEntry) -> bool { - !ignore_entry_is_dir(dent) -} - -/// Returns true if and only if the given `ignore::DirEntry` points to a -/// file. -#[cfg(not(windows))] -fn ignore_entry_is_file(dent: &ignore::DirEntry) -> bool { - dent.file_type().map_or(false, |ft| ft.is_file()) -} - -fn is_stdout_file( - dent: &ignore::DirEntry, - stdout_handle: Option<&same_file::Handle>, -) -> bool { - let stdout_handle = match stdout_handle { - None => return false, - Some(stdout_handle) => stdout_handle, - }; - // If we know for sure that these two things aren't equal, then avoid - // the costly extra stat call to determine equality. - if !maybe_dent_eq_handle(dent, stdout_handle) { - return false; - } - match same_file::Handle::from_path(dent.path()) { - Ok(h) => stdout_handle == &h, - Err(err) => { - message!("{}: {}", dent.path().display(), err); - false + stdout.write_all(glob.as_bytes())?; + first = false; } + stdout.write_all(b"\n")?; } -} - -#[cfg(unix)] -fn maybe_dent_eq_handle( - dent: &ignore::DirEntry, - handle: &same_file::Handle, -) -> bool { - dent.ino() == Some(handle.ino()) -} - -#[cfg(not(unix))] -fn maybe_dent_eq_handle(_: &ignore::DirEntry, _: &same_file::Handle) -> bool { - true -} - -fn eprint_nothing_searched() { - message!( - "No files were searched, which means ripgrep probably \ - applied a filter you didn't expect. \ - Try running again with --debug."); -} - -fn print_stats( - match_count: u64, - paths_searched: u64, - paths_matched: u64, - time_elapsed: Duration, -) { - let time_elapsed = - time_elapsed.as_secs() as f64 - + (time_elapsed.subsec_nanos() as f64 * 1e-9); - println!("\n{} matched lines\n\ - {} files contained matches\n\ - {} files searched\n\ - {:.3} seconds", match_count, paths_matched, - paths_searched, time_elapsed); -} - -// The Rust standard library suppresses the default SIGPIPE behavior, so that -// writing to a closed pipe doesn't kill the process. The goal is to instead -// handle errors through the normal result mechanism. Ripgrep needs some -// refactoring before it will be able to do that, however, so we re-enable the -// standard SIGPIPE behavior as a workaround. See -// https://github.com/BurntSushi/ripgrep/issues/200. -#[cfg(unix)] -fn reset_sigpipe() { - unsafe { - libc::signal(libc::SIGPIPE, libc::SIG_DFL); - } -} - -#[cfg(not(unix))] -fn reset_sigpipe() { - // no-op + Ok(count > 0) } |