// This module provides routines for reading ripgrep config "rc" files. The // primary output of these routines is a sequence of arguments, where each // argument corresponds precisely to one shell argument. use std::env; use std::fs::File; use std::io; use std::ffi::OsString; use std::path::{Path, PathBuf}; use bstr::io::BufReadExt; use log; use snafu::{self, ResultExt}; use crate::err::{self, Error, Result}; /// Return a sequence of arguments derived from ripgrep rc configuration files. pub fn args() -> Vec { let config_path = match env::var_os("RIPGREP_CONFIG_PATH") { None => return vec![], Some(config_path) => { if config_path.is_empty() { return vec![]; } PathBuf::from(config_path) } }; let (args, errs) = match parse(&config_path) { Ok((args, errs)) => (args, errs), Err(err) => { message!("{}", err); return vec![]; } }; if !errs.is_empty() { for err in errs { message!("{}:{}", config_path.display(), err); } } log::debug!( "{}: arguments loaded from config file: {:?}", config_path.display(), args ); args } /// Parse a single ripgrep rc file from the given path. /// /// On success, this returns a set of shell arguments, in order, that should /// be pre-pended to the arguments given to ripgrep at the command line. /// /// If the file could not be read, then an error is returned. If there was /// a problem parsing one or more lines in the file, then errors are returned /// for each line in addition to successfully parsed arguments. fn parse>( path: P, ) -> Result<(Vec, Vec)> { let path = path.as_ref(); 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. /// /// Callers should not provided a buffered reader, as this routine will use its /// own buffer internally. /// /// On success, this returns a set of shell arguments, in order, that should /// be pre-pended to the arguments given to ripgrep at the command line. /// /// If the reader could not be read, then an error is returned. If there was a /// problem parsing one or more lines, then errors are returned for each line /// in addition to successfully parsed arguments. fn parse_reader( rdr: R, ) -> io::Result<(Vec, Vec)> { let bufrdr = io::BufReader::new(rdr); let (mut args, mut errs) = (vec![], vec![]); let mut line_number = 0u64; bufrdr.for_byte_line_with_terminator(|line| { line_number += 1; let line = line.trim(); if line.is_empty() || line[0] == b'#' { return Ok(true); } match line.to_os_str() { Ok(osstr) => { args.push(osstr.to_os_string()); } Err(err) => { let ctx = snafu::Context { error: err, context: err::ConfigInvalidUTF8 { line_number }, }; errs.push(ctx.into()); } } Ok(true) })?; Ok((args, errs)) } #[cfg(test)] mod tests { use std::ffi::OsString; use super::parse_reader; #[test] fn basic() { let (args, errs) = parse_reader(&b"\ # Test --context=0 --smart-case -u # --bar --foo "[..]).unwrap(); assert!(errs.is_empty()); let args: Vec = args.into_iter().map(|s| s.into_string().unwrap()).collect(); assert_eq!(args, vec![ "--context=0", "--smart-case", "-u", "--foo", ]); } // We test that we can handle invalid UTF-8 on Unix-like systems. #[test] #[cfg(unix)] fn error() { use std::os::unix::ffi::OsStringExt; let (args, errs) = parse_reader(&b"\ quux foo\xFFbar baz "[..]).unwrap(); assert!(errs.is_empty()); assert_eq!(args, vec![ OsString::from("quux"), OsString::from_vec(b"foo\xFFbar".to_vec()), OsString::from("baz"), ]); } // ... but test that invalid UTF-8 fails on Windows. #[test] #[cfg(not(unix))] fn error() { let (args, errs) = parse_reader(&b"\ quux foo\xFFbar baz "[..]).unwrap(); assert_eq!(errs.len(), 1); assert_eq!(args, vec![ OsString::from("quux"), OsString::from("baz"), ]); } }