use anyhow::*; use log::*; use serde::{Deserialize, Serialize}; use std::ffi::OsString; use std::{iter::IntoIterator, str::FromStr}; use structopt::StructOpt; #[derive(Debug, Deserialize, Serialize)] struct ReadableBytesCount(i64); fn parse_readable_bytes_str(s: &str) -> Result { let suffix = s.chars().last(); if let Some(suffix) = suffix { match suffix { 'k' | 'M' | 'G' => i64::from_str(s.trim_end_matches(suffix)) .with_context(|| format!("Could not parse int")) .map(|e| { e * match suffix { 'k' => 1000, 'M' => 1000_000, 'G' => 1000_000_000, _ => panic!("impossible"), } }), _ => i64::from_str(s).with_context(|| format!("Could not parse int")), } } else { Err(format_err!("empty byte input")) } } fn is_default(t: &T) -> bool { t == &T::default() } // ugly, but serde and structopt use different methods to define defaults, so need to declare defaults twice macro_rules! set_default { ($name:ident, $val:expr, $type:ty) => { paste::item! { fn []() -> $type { $val } fn [](e: &$type) -> bool { e == &[]() } } }; } set_default!(cache_compression_level, 12, u32); set_default!(cache_max_blob_len, 2000000, i64); set_default!(max_archive_recursion, 4, i32); #[derive(StructOpt, Debug, Deserialize, Serialize)] #[structopt( name = "ripgrep-all", rename_all = "kebab-case", about = env!("CARGO_PKG_DESCRIPTION"), author = env!("CARGO_PKG_HOMEPAGE"), // TODO: long_about does not seem to work to only show this on short help after_help = "-h shows a concise overview, --help shows more detail and advanced options.\n\nAll other options not shown here are passed directly to rg, especially [PATTERN] and [PATH ...]", usage = "rga [RGA OPTIONS] [RG OPTIONS] PATTERN [PATH ...]" )] pub struct RgaArgs { #[serde(default, skip_serializing_if = "is_default")] #[structopt(long = "--rga-no-cache")] /// Disable caching of results /// /// By default, rga caches the extracted text, if it is small enough, /// to a database in ~/.cache/rga on Linux, /// ~/Library/Caches/rga on macOS, /// or C:\Users\username\AppData\Local\rga on Windows. /// This way, repeated searches on the same set of files will be much faster. /// If you pass this flag, all caching will be disabled. pub no_cache: bool, #[serde(default, skip_serializing_if = "is_default")] #[structopt(long = "--rga-accurate")] /// Use more accurate but slower matching by mime type /// /// By default, rga will match files using file extensions. /// Some programs, such as sqlite3, don't care about the file extension at all, /// so users sometimes use any or no extension at all. With this flag, rga /// will try to detect the mime type of input files using the magic bytes /// (similar to the `file` utility), and use that to choose the adapter. /// Detection is only done on the first 8KiB of the file, since we can't always seek on the input (in archives). pub accurate: bool, #[serde(default, skip_serializing_if = "is_default")] #[structopt( long = "--rga-adapters", require_equals = true, require_delimiter = true )] /// Change which adapters to use and in which priority order (descending) /// /// "foo,bar" means use only adapters foo and bar. /// "-bar,baz" means use all default adapters except for bar and baz. /// "+bar,baz" means use all default adapters and also bar and baz. pub adapters: Vec, #[serde( default = "def_cache_max_blob_len", skip_serializing_if = "def_cache_max_blob_len_if" )] #[structopt( long = "--rga-cache-max-blob-len", default_value = "2000000", hidden_short_help = true, require_equals = true, parse(try_from_str = parse_readable_bytes_str) )] /// Max compressed size to cache /// /// Longest byte length (after compression) to store in cache. Longer adapter outputs will not be cached and recomputed every time. Allowed suffixes: k M G pub cache_max_blob_len: i64, #[serde( default = "def_cache_compression_level", skip_serializing_if = "def_cache_compression_level_if" )] #[structopt( long = "--rga-cache-compression-level", hidden_short_help = true, default_value = "12", require_equals = true, help = "" )] /// ZSTD compression level to apply to adapter outputs before storing in cache db /// /// Ranges from 1 - 22 pub cache_compression_level: u32, #[serde( default = "def_max_archive_recursion", skip_serializing_if = "def_max_archive_recursion_if" )] #[structopt( long = "--rga-max-archive-recursion", default_value = "4", require_equals = true, help = "Maximum nestedness of archives to recurse into", hidden_short_help = true )] pub max_archive_recursion: i32, #[serde(skip)] #[structopt(long = "--rga-fzf-path", require_equals = true, hidden = true)] /// same as passing path directly, except if argument is empty /// kinda hacky, but if no file is found, fzf calls rga with empty string as path, which causes No such file or directory from rg. So filter those cases and return specially pub fzf_path: Option, // these arguments stop the process, so don't serialize them #[serde(skip)] #[structopt(long = "--rga-list-adapters", help = "List all known adapters")] pub list_adapters: bool, #[serde(skip)] #[structopt(long, help = "Show help for ripgrep itself")] pub rg_help: bool, #[serde(skip)] #[structopt(long, help = "Show version of ripgrep itself")] pub rg_version: bool, } static RGA_CONFIG: &str = "RGA_CONFIG"; pub fn parse_args(args: I) -> Result where I: IntoIterator, I::Item: Into + Clone, { match std::env::var(RGA_CONFIG) { Ok(val) => { debug!( "Loading args from env {}={}, ignoring cmd args", RGA_CONFIG, val ); Ok(serde_json::from_str(&val)?) } Err(_) => { let matches = RgaArgs::from_iter(args); let serialized_config = serde_json::to_string(&matches)?; std::env::set_var(RGA_CONFIG, &serialized_config); debug!("{}={}", RGA_CONFIG, serialized_config); Ok(matches) } } } /// Split arguments into the ones we care about and the ones rg cares about pub fn split_args() -> Result<(RgaArgs, Vec)> { let mut app = RgaArgs::clap(); app.p.create_help_and_version(); let mut firstarg = true; // debug!("{:#?}", app.p.flags); let (our_args, mut passthrough_args): (Vec, Vec) = std::env::args_os() .partition(|os_arg| { if firstarg { // hacky, but .enumerate() would be ugly because partition is too simplistic firstarg = false; return true; } if let Some(arg) = os_arg.to_str() { arg.starts_with("--rga-") || arg.starts_with("--rg-") || arg == "--help" || arg == "-h" || arg == "--version" || arg == "-V" } else { // args that are not unicode can only be filenames, pass them to rg false } }); debug!("our_args: {:?}", our_args); let matches = parse_args(our_args)?; if matches.rg_help { passthrough_args.insert(0, "--help".into()); } if matches.rg_version { passthrough_args.insert(0, "--version".into()); } debug!("passthrough_args: {:?}", passthrough_args); Ok((matches, passthrough_args)) }