summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Gallant <jamslam@gmail.com>2019-04-25 07:20:24 -0400
committerAndrew Gallant <jamslam@gmail.com>2019-05-29 18:07:03 -0400
commit7bf7ceb5d3953a38a6fad45110af2e4117c778ca (patch)
treec0c664902040c3594046130b8afc9a30e1bbec23
parent7d3f79458891d2cb9ce2b3d51f8d8f788d36c9e8 (diff)
progressag/snafu
-rw-r--r--Cargo.lock52
-rw-r--r--Cargo.toml3
-rw-r--r--src/args.rs182
-rw-r--r--src/config.rs23
-rw-r--r--src/err.rs250
-rw-r--r--src/logger.rs7
-rw-r--r--src/main.rs45
-rw-r--r--src/path_printer.rs9
-rw-r--r--tests/regression.rs4
9 files changed, 438 insertions, 137 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 49dbc721..53082d76 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,6 +24,28 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "backtrace"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "base64"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -540,10 +562,16 @@ dependencies = [
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "snafu 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
+name = "rustc-demangle"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "ryu"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -587,6 +615,25 @@ version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "snafu"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "snafu-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "snafu-derive"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -718,6 +765,8 @@ dependencies = [
"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
+"checksum backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f106c02a3604afcdc0df5d36cc47b44b55917dbaf3d808f71c163a0ddba64637"
+"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum bstr 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6c8203ca06c502958719dae5f653a79e0cc6ba808ed02beffbf27d09610f2143"
@@ -767,12 +816,15 @@ dependencies = [
"checksum regex-automata 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a25a7daa2eea48550e9946133d6cc9621020d29cc7069089617234bf8b6a8693"
"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
+"checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288"
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
"checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4"
"checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79"
"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
"checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be"
+"checksum snafu 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67a2e16c9b74a09d4bc84ea6e5eb68fef70b4cb14e8ccd26f2de76af2e9c2e8a"
+"checksum snafu-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bf96650c2b31fa949780f792025f4ca79fbe87bd723a8b3573dce37be7193b08"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b4cfac95805274c6afdb12d8f770fa2d27c045953e7b630a81801953699a9a"
"checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a"
diff --git a/Cargo.toml b/Cargo.toml
index 7bd62d4f..1a0ad2e2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,10 +50,11 @@ bstr = "0.1.2"
grep = { version = "0.2.4", path = "grep" }
ignore = { version = "0.4.7", path = "ignore" }
lazy_static = "1.1.0"
-log = "0.4.5"
+log = { version = "0.4.5", features = ["std"] }
num_cpus = "1.8.0"
regex = "1.0.5"
serde_json = "1.0.23"
+snafu = "0.2.3"
termcolor = "1.0.3"
[dependencies.clap]
diff --git a/src/args.rs b/src/args.rs
index cd217b4b..4ba0e540 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -36,6 +36,7 @@ use ignore::{Walk, WalkBuilder, WalkParallel};
use log;
use num_cpus;
use regex;
+use snafu::{self, ResultExt};
use termcolor::{
WriteColor,
BufferWriter, ColorChoice,
@@ -43,6 +44,7 @@ use termcolor::{
use crate::app;
use crate::config;
+use crate::err::{self, Result};
use crate::logger::Logger;
use crate::messages::{set_messages, set_ignore_messages};
use crate::path_printer::{PathPrinter, PathPrinterBuilder};
@@ -50,7 +52,6 @@ use crate::search::{
PatternMatcher, Printer, SearchWorker, SearchWorkerBuilder,
};
use crate::subject::SubjectBuilder;
-use crate::Result;
/// The command that ripgrep should execute based on the command line
/// configuration.
@@ -141,9 +142,7 @@ impl Args {
set_messages(!early_matches.is_present("no-messages"));
set_ignore_messages(!early_matches.is_present("no-ignore-messages"));
- if let Err(err) = Logger::init() {
- return Err(format!("failed to initialize logger: {}", err).into());
- }
+ Logger::init()?;
if early_matches.is_present("trace") {
log::set_max_level(log::LevelFilter::Trace);
} else if early_matches.is_present("debug") {
@@ -422,13 +421,22 @@ impl SortBy {
match self.kind {
SortByKind::None | SortByKind::Path => {}
SortByKind::LastModified => {
- env::current_exe()?.metadata()?.modified()?;
+ env::current_exe()
+ .and_then(|x| x.metadata())
+ .and_then(|x| x.modified())
+ .context(err::UnsupportedSortModified)?;
}
SortByKind::LastAccessed => {
- env::current_exe()?.metadata()?.accessed()?;
+ env::current_exe()
+ .and_then(|x| x.metadata())
+ .and_then(|x| x.accessed())
+ .context(err::UnsupportedSortAccess)?;
}
SortByKind::Created => {
- env::current_exe()?.metadata()?.created()?;
+ env::current_exe()
+ .and_then(|x| x.metadata())
+ .and_then(|x| x.created())
+ .context(err::UnsupportedSortCreation)?;
}
}
Ok(())
@@ -611,21 +619,12 @@ impl ArgMatches {
Ok(matcher) => return Ok(PatternMatcher::PCRE2(matcher)),
Err(err) => err,
};
- Err(From::from(format!(
- "regex could not be compiled with either the default regex \
- engine or with PCRE2.\n\n\
- default regex engine error:\n{}\n{}\n{}\n\n\
- PCRE2 regex engine error:\n{}",
- "~".repeat(79), rust_err, "~".repeat(79), pcre_err,
- )))
+ err::Hybrid {
+ rust_err: Box::new(rust_err),
+ pcre_err: Box::new(pcre_err),
+ }.fail()
} else {
- let matcher = match self.matcher_rust(patterns) {
- Ok(matcher) => matcher,
- Err(err) => {
- return Err(From::from(suggest_pcre2(err.to_string())));
- }
- };
- Ok(PatternMatcher::RustRegex(matcher))
+ Ok(PatternMatcher::RustRegex(self.matcher_rust(patterns)?))
}
}
@@ -635,11 +634,7 @@ impl ArgMatches {
/// then this returns an error.
#[cfg(not(feature = "pcre2"))]
fn matcher(&self, patterns: &[String]) -> Result<PatternMatcher> {
- if self.is_present("pcre2") {
- return Err(From::from(
- "PCRE2 is not available in this build of ripgrep",
- ));
- }
+ snafu::ensure!(!self.is_present("pcre2"), err::PCRE2Unavailable);
let matcher = self.matcher_rust(patterns)?;
Ok(PatternMatcher::RustRegex(matcher))
}
@@ -692,10 +687,7 @@ impl ArgMatches {
} else {
builder.build(&patterns.join("|"))
};
- match res {
- Ok(m) => Ok(m),
- Err(err) => Err(From::from(suggest_multiline(err.to_string()))),
- }
+ res.eager_context(err::RustRegex)
}
/// Build a matcher using PCRE2.
@@ -737,7 +729,7 @@ impl ArgMatches {
if self.is_present("crlf") {
builder.crlf(true);
}
- Ok(builder.build(&patterns.join("|"))?)
+ Ok(builder.build(&patterns.join("|")).context(err::PCRE2Regex)?)
}
/// Build a JSON printer that writes results to the given writer.
@@ -987,7 +979,7 @@ impl ArgMatches {
// Start with a default set of color specs.
let mut specs = default_color_specs();
for spec_str in self.values_of_lossy_vec("colors") {
- specs.push(spec_str.parse()?);
+ specs.push(spec_str.parse().context(err::InvalidColorSpec)?);
}
Ok(ColorSpecs::new(&specs))
}
@@ -1078,7 +1070,8 @@ impl ArgMatches {
return Ok(EncodingMode::Disabled);
}
- Ok(EncodingMode::Some(Encoding::new(&label)?))
+ let enc = Encoding::new(&label).context(err::SearchConfig)?;
+ Ok(EncodingMode::Some(enc))
}
/// Return the file separator to use based on the CLI configuration.
@@ -1267,16 +1260,17 @@ impl ArgMatches {
/// Builds the set of glob overrides from the command line flags.
fn overrides(&self) -> Result<Override> {
- let mut builder = OverrideBuilder::new(env::current_dir()?);
+ let cwd = env::current_dir().context(err::CurrentDir)?;
+ let mut builder = OverrideBuilder::new(cwd);
for glob in self.values_of_lossy_vec("glob") {
- builder.add(&glob)?;
+ builder.add(&glob).context(err::InvalidGlobFlag)?;
}
// This only enables case insensitivity for subsequent globs.
builder.case_insensitive(true).unwrap();
for glob in self.values_of_lossy_vec("iglob") {
- builder.add(&glob)?;
+ builder.add(&glob).context(err::InvalidIGlobFlag)?;
}
- Ok(builder.build()?)
+ Ok(builder.build().context(err::GlobBuild)?)
}
/// Return all file paths that ripgrep should search.
@@ -1330,16 +1324,10 @@ impl ArgMatches {
};
if sep.is_empty() {
Ok(None)
- } else if sep.len() > 1 {
- Err(From::from(format!(
- "A path separator must be exactly one byte, but \
- the given separator is {} bytes: {}\n\
- In some shells on Windows '/' is automatically \
- expanded. Use '//' instead.",
- sep.len(),
- cli::escape(&sep),
- )))
} else {
+ snafu::ensure!(sep.len() == 1, err::InvalidPathSeparator {
+ separator: sep.clone(),
+ });
Ok(Some(sep[0]))
}
}
@@ -1386,17 +1374,17 @@ impl ArgMatches {
}
if let Some(paths) = self.values_of_os("file") {
for path in paths {
- if path == "-" {
- pats.extend(cli::patterns_from_stdin()?
- .into_iter()
- .map(|p| self.pattern_from_string(p))
- );
- } else {
- pats.extend(cli::patterns_from_path(path)?
- .into_iter()
- .map(|p| self.pattern_from_string(p))
- );
- }
+ let some_pats =
+ if path == "-" {
+ cli::patterns_from_stdin()
+ .context(err::ReadManyPatterns)?
+ } else {
+ cli::patterns_from_path(path)
+ .context(err::ReadManyPatterns)?
+ };
+ pats.extend(
+ some_pats.into_iter().map(|p| self.pattern_from_string(p))
+ );
}
}
Ok(pats)
@@ -1417,7 +1405,7 @@ impl ArgMatches {
///
/// If the pattern is not valid UTF-8, then an error is returned.
fn pattern_from_os_str(&self, pat: &OsStr) -> Result<String> {
- let s = cli::pattern_from_os(pat)?;
+ let s = cli::pattern_from_os(pat).context(err::ReadOSPattern)?;
Ok(self.pattern_from_str(s))
}
@@ -1475,11 +1463,12 @@ impl ArgMatches {
/// flag. If no --pre-globs are available, then this always returns an
/// empty set of globs.
fn preprocessor_globs(&self) -> Result<Override> {
- let mut builder = OverrideBuilder::new(env::current_dir()?);
+ let cwd = env::current_dir().context(err::CurrentDir)?;
+ let mut builder = OverrideBuilder::new(cwd);
for glob in self.values_of_lossy_vec("pre-glob") {
- builder.add(&glob)?;
+ builder.add(&glob).context(err::InvalidPreGlob)?;
}
- Ok(builder.build()?)
+ Ok(builder.build().context(err::PreGlobBuild)?)
}
/// Parse the regex-size-limit argument option into a byte count.
@@ -1561,7 +1550,7 @@ impl ArgMatches {
builder.clear(&ty);
}
for def in self.values_of_lossy_vec("type-add") {
- builder.add_def(&def)?;
+ builder.add_def(&def).context(err::InvalidTypeDefinition)?;
}
for ty in self.values_of_lossy_vec("type") {
builder.select(&ty);
@@ -1569,7 +1558,7 @@ impl ArgMatches {
for ty in self.values_of_lossy_vec("type-not") {
builder.negate(&ty);
}
- builder.build().map_err(From::from)
+ builder.build().eager_context(err::TypeDefinitionBuild)
}
/// Returns the number of times the `unrestricted` flag is provided.
@@ -1628,7 +1617,11 @@ impl ArgMatches {
fn usize_of(&self, name: &str) -> Result<Option<usize>> {
match self.value_of_lossy(name) {
None => Ok(None),
- Some(v) => v.parse().map(Some).map_err(From::from),
+ Some(v) => {
+ v.parse()
+ .map(Some)
+ .eager_context(err::InvalidNumber { flag: name.to_string() })
+ }
}
}
@@ -1636,15 +1629,17 @@ impl ArgMatches {
///
/// If the aforementioned format is not recognized, then this returns an
/// error.
- fn parse_human_readable_size(
- &self,
- arg_name: &str,
- ) -> Result<Option<u64>> {
- let size = match self.value_of_lossy(arg_name) {
- None => return Ok(None),
- Some(size) => size,
- };
- Ok(Some(cli::parse_human_readable_size(&size)?))
+ fn parse_human_readable_size(&self, name: &str) -> Result<Option<u64>> {
+ match self.value_of_lossy(name) {
+ None => Ok(None),
+ Some(size) => {
+ cli::parse_human_readable_size(&size)
+ .map(Some)
+ .eager_context(err::InvalidHumanSize {
+ flag: name.to_string()
+ })
+ }
+ }
}
}
@@ -1678,32 +1673,6 @@ impl ArgMatches {
}
}
-/// Inspect an error resulting from building a Rust regex matcher, and if it's
-/// believed to correspond to a syntax error that PCRE2 could handle, then
-/// add a message to suggest the use of -P/--pcre2.
-#[cfg(feature = "pcre2")]
-fn suggest_pcre2(msg: String) -> String {
- if !msg.contains("backreferences") && !msg.contains("look-around") {
- msg
- } else {
- format!("{}
-
-Consider enabling PCRE2 with the --pcre2 flag, which can handle backreferences
-and look-around.", msg)
- }
-}
-
-fn suggest_multiline(msg: String) -> String {
- if msg.contains("the literal") && msg.contains("not allowed") {
- format!("{}
-
-Consider enabling multiline mode with the --multiline flag (or -U for short).
-When multiline mode is enabled, new line characters can be matched.", msg)
- } else {
- msg
- }
-}
-
/// Convert the result of parsing a human readable file size to a `usize`,
/// failing if the type does not fit.
fn u64_to_usize(
@@ -1716,11 +1685,14 @@ fn u64_to_usize(
None => return Ok(None),
Some(value) => value,
};
- if value <= usize::MAX as u64 {
- Ok(Some(value as usize))
- } else {
- Err(From::from(format!("number too large for {}", arg_name)))
- }
+ snafu::ensure!(
+ value <= usize::MAX as u64,
+ err::NumberTooBig {
+ flag: arg_name.to_string(),
+ limit: usize::MAX as u64,
+ }
+ );
+ Ok(Some(value as usize))
}
/// Builds a comparator for sorting two files according to a system time
@@ -1768,7 +1740,7 @@ where I: IntoIterator<Item=T>,
Err(err) => err,
};
if err.use_stderr() {
- return Err(err.into());
+ return Err(snafu::Context { error: err, context: err::Clap }.into());
}
// Explicitly ignore any error returned by write!. The most likely error
// at this point is a broken pipe error, in which case, we want to ignore
diff --git a/src/config.rs b/src/config.rs
index a5e492ec..6c9b9b32 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -3,7 +3,6 @@
// argument corresponds precisely to one shell argument.
use std::env;
-use std::error::Error;
use std::fs::File;
use std::io;
use std::ffi::OsString;
@@ -11,8 +10,9 @@ use std::path::{Path, PathBuf};
use bstr::io::BufReadExt;
use log;
+use snafu::{self, ResultExt};
-use crate::Result;
+use crate::err::{self, Error, Result};
/// Return a sequence of arguments derived from ripgrep rc configuration files.
pub fn args() -> Vec<OsString> {
@@ -55,12 +55,11 @@ pub fn args() -> Vec<OsString> {
/// for each line in addition to successfully parsed arguments.
fn parse<P: AsRef<Path>>(
path: P,
-) -> Result<(Vec<OsString>, Vec<Box<Error>>)> {
+) -> Result<(Vec<OsString>, Vec<Error>)> {
let path = path.as_ref();
- match File::open(&path) {
- Ok(file) => parse_reader(file),
- Err(err) => Err(From::from(format!("{}: {}", path.display(), err))),
- }
+ let file = File::open(&path).context(err::ConfigIO { path })?;
+ let res = parse_reader(file).context(err::ConfigIO { path })?;
+ Ok(res)
}
/// Parse a single ripgrep rc file from the given reader.
@@ -76,10 +75,10 @@ fn parse<P: AsRef<Path>>(
/// in addition to successfully parsed arguments.
fn parse_reader<R: io::Read>(
rdr: R,
-) -> Result<(Vec<OsString>, Vec<Box<Error>>)> {
+) -> io::Result<(Vec<OsString>, Vec<Error>)> {
let bufrdr = io::BufReader::new(rdr);
let (mut args, mut errs) = (vec![], vec![]);
- let mut line_number = 0;
+ let mut line_number = 0u64;
bufrdr.for_byte_line_with_terminator(|line| {
line_number += 1;
@@ -92,7 +91,11 @@ fn parse_reader<R: io::Read>(
args.push(osstr.to_os_string());
}
Err(err) => {
- errs.push(format!("{}: {}", line_number, err).into());
+ let ctx = snafu::Context {
+ error: err,
+ context: err::ConfigInvalidUTF8 { line_number },
+ };
+ errs.push(ctx.into());
}
}
Ok(true)
diff --git a/src/err.rs b/src/err.rs
new file mode 100644
index 00000000..26919593
--- /dev/null
+++ b/src/err.rs
@@ -0,0 +1,250 @@
+use std::io;
+use std::path::{Path, PathBuf};
+use std::result;
+
+use snafu::{Backtrace, Context, Snafu};
+
+pub type Result<T> = result::Result<T, Error>;
+
+#[derive(Debug, Snafu)]
+#[snafu(visibility(pub(crate)))]
+pub enum Error {
+ #[snafu(display("I/O error: {}", source))]
+ IO {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+ #[snafu(display(
+ "failed to retrieve current working directory: {}", source,
+ ))]
+ CurrentDir {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+ #[snafu(display("sorting by modified time is unsupported: {}", source))]
+ UnsupportedSortModified {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+ #[snafu(display("sorting by access time is unsupported: {}", source))]
+ UnsupportedSortAccess {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+ #[snafu(display("sorting by creation time is unsupported: {}", source))]
+ UnsupportedSortCreation {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+ #[snafu(display(
+ "I/O error parsing config in {}: {}",
+ path.display(),
+ source,
+ ))]
+ ConfigIO {
+ backtrace: Backtrace,
+ source: io::Error,
+ path: PathBuf,
+ },
+ #[snafu(display(
+ "config parse error on line {}: {}",
+ line_number,
+ source,
+ ))]
+ ConfigInvalidUTF8 {
+ backtrace: Backtrace,
+ source: bstr::Utf8Error,
+ line_number: u64,
+ },
+ #[snafu(display("failed to initialize logger: {}", source))]
+ LoggerInit {
+ backtrace: Backtrace,
+ source: log::SetLoggerError,
+ },
+ #[snafu(display("{}", source))]
+ Clap {
+ backtrace: Backtrace,
+ source: clap::Error,
+ },
+ #[snafu(display("{}", source))]
+ ReadManyPatterns {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+ #[snafu(display("{}", source))]
+ ReadOSPattern {
+ backtrace: Backtrace,
+ source: grep::cli::InvalidPatternError,
+ },
+ #[snafu(display(
+ "A path separator must be exactly one byte, but \
+ the given separator is {} bytes: {}\n\
+ In some shells on Windows '/' is automatically \
+ expanded. Use '//' instead.",
+ separator.len(), grep::cli::escape(&separator),
+ ))]
+ InvalidPathSeparator {
+ backtrace: Backtrace,
+ separator: Vec<u8>,
+ },
+ #[snafu(display("error parsing -g/--glob: {}", source))]
+ InvalidGlobFlag {
+ backtrace: Backtrace,
+ source: ignore::Error,
+ },
+ #[snafu(display("error parsing --iglob: {}", source))]
+ InvalidIGlobFlag {
+ backtrace: Backtrace,
+ source: ignore::Error,
+ },
+ #[snafu(display("error building --glob matcher: {}", source))]
+ GlobBuild {
+ backtrace: Backtrace,
+ source: ignore::Error,
+ },
+ #[snafu(display("error parsing --pre-glob: {}", source))]
+ InvalidPreGlob {
+ backtrace: Backtrace,
+ source: ignore::Error,
+ },
+ #[snafu(display("error building --pre-glob matcher: {}", source))]
+ PreGlobBuild {
+ backtrace: Backtrace,
+ source: ignore::Error,
+ },
+ #[snafu(display("error parsing --type-add value: {}", source))]
+ InvalidTypeDefinition {
+ backtrace: Backtrace,
+ source: ignore::Error,
+ },
+ #[snafu(display("error building type matcher: {}", source))]
+ TypeDefinitionBuild {
+ backtrace: Backtrace,
+ source: ignore::Error,
+ },
+ #[snafu(display("failed to parse {} value as a number: {}", flag, source))]
+ InvalidNumber {
+ backtrace: Backtrace,
+ source: std::num::ParseIntError,
+ flag: String,
+ },
+ #[snafu(display(
+ "failed to parse {} value as a file size: {}", flag, source,
+ ))]
+ InvalidHumanSize {
+ backtrace: Backtrace,
+ source: grep::cli::ParseSizeError,
+ flag: String,
+ },
+ #[snafu(display(
+ "number given to {} is too large (limit is {})",
+ flag, limit,
+ ))]
+ NumberTooBig {
+ backtrace: Backtrace,
+ flag: String,
+ limit: u64,
+ },
+ #[snafu(display("{}", source))]
+ SearchConfig {
+ backtrace: Backtrace,
+ source: grep::searcher::ConfigError,
+ },
+ #[snafu(display("invalid --colors spec: {}", source))]
+ InvalidColorSpec {
+ backtrace: Backtrace,
+ source: grep::printer::ColorError,
+ },
+ #[snafu(display("{}", suggest(source)))]
+ RustRegex {
+ backtrace: Backtrace,
+ source: grep::regex::Error,
+ },
+ #[snafu(display(
+ "regex could not be compiled with either the default regex \
+ engine or with PCRE2.\n\n\
+ default regex engine error:\n{}\n{}\n{}\n\n\
+ PCRE2 regex engine error:\n{}",
+ "~".repeat(79),
+ rust_err,
+ "~".repeat(79),
+ pcre_err,
+ ))]
+ Hybrid {
+ backtrace: Backtrace,
+ rust_err: Box<Error>,
+ pcre_err: Box<Error>,
+ },
+ #[snafu(display("failed to write type definitions to stdout: {}", source))]
+ WriteTypes {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+ #[cfg(feature = "pcre2")]
+ #[snafu(display("{}", suggest(source)))]
+ PCRE2Regex {
+ backtrace: Backtrace,
+ source: grep::pcre2::Error,
+ },
+ #[snafu(display("PCRE2 is not available in this build of ripgrep"))]
+ PCRE2Unavailable {
+ backtrace: Backtrace,
+ },
+ #[snafu(display("failed to write PCRE2 version to stdout: {}", source))]
+ PCRE2Version {
+ backtrace: Backtrace,
+ source: io::Error,
+ },
+}
+
+impl Error {
+ /// Return true if and only if this corresponds to an I/O error generated
+ /// by a broken pipe.
+ pub fn is_broken_pipe(&self) -> bool {
+ self.io_err().map_or(false, |e| e.kind() == io::ErrorKind::BrokenPipe)
+ }
+
+ /// Return a reference to this error's underlying I/O error, if one exists.
+ pub fn io_err(&self) -> Option<&io::Error> {
+ match *self {
+ Error::IO { ref source, .. } => Some(source),
+ Error::CurrentDir { ref source, .. } => Some(source),
+ Error::UnsupportedSortModified { ref source, .. } => Some(source),
+ Error::UnsupportedSortAccess { ref source, .. } => Some(source),
+ Error::UnsupportedSortCreation { ref source, .. } => Some(source),
+ Error::ConfigIO { ref source, .. } => Some(source),
+ Error::ReadManyPatterns { ref source, .. } => Some(source),
+ Error::WriteTypes { ref source, .. } => Some(source),
+ Error::PCRE2Version { ref source, .. } => Some(source),
+ _ => None,
+ }
+ }
+}
+
+/// Inspect an error's display string and look for potentially suggestions
+/// to give to an end user.
+///
+/// These include:
+///
+/// 1. If the error results from the use of a new line literal, then return a
+/// new message suggesting the use of the -U/--multiline flag.
+/// 2. If the error correspond to a syntax error that PCRE2 could handle, then
+/// add a message to suggest the use of -P/--pcre2.
+fn suggest<E: std::error::Error>(err: &E) -> String {
+ let msg = err.to_string();
+ if msg.contains("the literal") && msg.contains("not allowed") {
+ format!("{}
+
+Consider enabling multiline mode with the --multiline flag (or -U for short).
+When multiline mode is enabled, new line characters can be matched.", msg)
+ } else if cfg!(feature = "pcre2") &&
+ (msg.contains("backreferences") || msg.contains("look-around"))
+ {
+ format!("{}
+
+Consider enabling PCRE2 with the --pcre2 flag, which can handle backreferences
+and look-around.", msg)
+ } else {
+ msg
+ }
+}
diff --git a/src/logger.rs b/src/logger.rs
index f12f0b19..bf3f677c 100644
--- a/src/logger.rs
+++ b/src/logger.rs
@@ -4,6 +4,9 @@
// for this functionality.
use log::{self, Log};
+use snafu::ResultExt;
+
+use crate::err::{self, Result};
/// The simplest possible logger that logs to stderr.
///
@@ -18,8 +21,8 @@ impl Logger {
/// Create a new logger that logs to stderr and initialize it as the
/// global logger. If there was a problem setting the logger, then an
/// error is returned.
- pub fn init() -> Result<(), log::SetLoggerError> {
- log::set_logger(LOGGER)
+ pub fn init() -> Result<()> {
+ log::set_logger(LOGGER).eager_context(err::LoggerInit)
}
}
diff --git a/src/main.rs b/src/main.rs
index bed33296..a0a69ccf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,15 @@
+#![allow(warnings)]
+
use std::io::{self, Write};
use std::process;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use ignore::WalkState;
+use snafu::ResultExt;
use args::Args;
+use err::Result;
use subject::Subject;
#[macro_use]
@@ -14,6 +18,7 @@ mod messages;
mod app;
mod args;
mod config;
+mod err;
mod logger;
mod path_printer;
mod search;
@@ -42,7 +47,8 @@ mod subject;
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
-type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
+// type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
+// type Result<T> =
fn main() {
if let Err(err) = Args::parse().and_then(try_main) {
@@ -213,12 +219,12 @@ fn files(args: &Args) -> Result<bool> {
}
if let Err(err) = path_printer.write_path(subject.path()) {
// A broken pipe means graceful termination.
- if err.kind() == io::ErrorKind::BrokenPipe {
+ if err.is_broken_pipe() {
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());
+ return Err(err);
}
}
Ok(matched)
@@ -239,7 +245,7 @@ fn files_parallel(args: &Args) -> Result<bool> {
let matched = Arc::new(AtomicBool::new(false));
let (tx, rx) = mpsc::channel::<Subject>();
- let print_thread = thread::spawn(move || -> io::Result<()> {
+ let print_thread = thread::spawn(move || -> Result<()> {
for subject in rx.iter() {
path_printer.write_path(subject.path())?;
}
@@ -271,8 +277,8 @@ fn files_parallel(args: &Args) -> Result<bool> {
// 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());
+ if !err.is_broken_pipe() {
+ return Err(err);
}
}
Ok(matched.load(SeqCst))
@@ -280,22 +286,29 @@ fn files_parallel(args: &Args) -> Result<bool> {
/// The top-level entry point for --type-list.
fn types(args: &Args) -> Result<bool> {
+ write_types(args.stdout(), &args.type_defs()?)
+ .eager_context(err::WriteTypes)
+}
+
+fn write_types<W: io::Write>(
+ mut wtr: W,
+ defs: &[ignore::types::FileTypeDef],
+) -> io::Result<bool> {
let mut count = 0;
- let mut stdout = args.stdout();
- for def in args.type_defs()? {
+ for def in defs {
count += 1;
- stdout.write_all(def.name().as_bytes())?;
- stdout.write_all(b": ")?;
+ wtr.write_all(def.name().as_bytes())?;
+ wtr.write_all(b": ")?;
let mut first = true;
for glob in def.globs() {
if !first {
- stdout.write_all(b", ")?;