diff --git a/Cargo.lock b/Cargo.lock index a5bb3871e..d50280fd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" -version = "2.29.0" +version = "2.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -50,7 +50,6 @@ dependencies = [ "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -67,14 +66,6 @@ dependencies = [ "simd 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "env_logger" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "fnv" version = "1.0.6" @@ -106,7 +97,7 @@ dependencies = [ "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -115,7 +106,7 @@ dependencies = [ name = "grep" version = "0.1.7" dependencies = [ - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -128,7 +119,7 @@ dependencies = [ "crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.2.1", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "same-file 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -150,15 +141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -235,15 +218,14 @@ version = "0.7.1" dependencies = [ "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "bytecount 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.29.2 (registry+https://github.com/rust-lang/crates.io-index)", "encoding_rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.2.1", "grep 0.1.7", "ignore 0.3.1", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -331,11 +313,6 @@ name = "utf8-ranges" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "vec_map" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "void" version = "1.0.2" @@ -383,18 +360,16 @@ dependencies = [ "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" "checksum bytecount 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "882585cd7ec84e902472df34a5e01891202db3bf62614e1f0afe459c1afcf744" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" -"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f" +"checksum clap 2.29.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4151c5790817c7d21bbdc6c3530811f798172915f93258244948b93ba19604a6" "checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" "checksum encoding_rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f5215aabf22b83153be3ee44dfe3f940214541b2ce13d419c55e7a115c8c51a9" -"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd510087c325af53ba24f3be8f1c081b0982319adcb8b03cad764512923ccc19" "checksum fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "08b3a6f13ad6b96572b53ce7af74543132f1a7055ccceb6d073dd36c54481859" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0" -"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a89a0c46ba789b8a247d4c567aed4d7c68e624672d238b45cc3ec20dc9f940" +"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" @@ -413,7 +388,6 @@ dependencies = [ "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" -"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b167e9a4420d8dddb260e70c90a4a375a1e5691f21f70e715553da87b6c2503a" "checksum winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "890b38836c01d72fdb636d15c9cfc52ec7fd783b330abc93cd1686f4308dfccc" diff --git a/Cargo.toml b/Cargo.toml index e6f73ef4b..b36bac889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,14 +35,12 @@ members = [ "grep", "globset", "ignore", "termcolor", "wincolor" ] [dependencies] atty = "0.2.2" bytecount = "0.3.1" -clap = "2.26" encoding_rs = "0.7" -env_logger = { version = "0.4", default-features = false } grep = { version = "0.1.7", path = "grep" } ignore = { version = "0.3.1", path = "ignore" } lazy_static = "1" libc = "0.2" -log = "0.3" +log = "0.4" memchr = "2" memmap = "0.6" num_cpus = "1" @@ -51,14 +49,23 @@ same-file = "1" termcolor = { version = "0.3.3", path = "termcolor" } globset = { version = "0.2.1", path = "globset" } +[dependencies.clap] +version = "2.26" +default-features = false +features = ["suggestions", "color"] + [target.'cfg(windows)'.dependencies.winapi] version = "0.3" features = ["std", "winnt"] [build-dependencies] -clap = "2.26" lazy_static = "1" +[build-dependencies.clap] +version = "2.26" +default-features = false +features = ["suggestions", "color"] + [features] avx-accel = ["bytecount/avx-accel"] simd-accel = [ diff --git a/README.md b/README.md index ac6768639..502cfa5c0 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,50 @@ extensions. The syntax supported is [documented as part of Rust's regex library](https://doc.rust-lang.org/regex/regex/index.html#syntax). +### Configuration files + +ripgrep supports reading configuration files that change ripgrep's default +behavior. The format of the configuration file is an "rc" style and is very +simple. It is defined by two rules: + +1. Every line is a shell argument, after trimming ASCII whitespace. +2. Lines starting with '#' (optionally preceded by any amount of + ASCII whitespace) are ignored. + +ripgrep will look for a single configuration file if and only if the +`RIPGREP_CONFIG_PATH` environment variable is set and is non-empty. ripgrep +will parse shell arguments from this file on startup and will behave as if +the arguments in this file were prepended to any explicit arguments given to +ripgrep on the command line. + +For example, if your ripgreprc file contained a single line: + + --smart-case + +then the following command + + RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo + +would behave identically to the following command + + rg --smart-case foo + +ripgrep also provides a flag, --no-config, that when present will suppress +any and all support for configuration. This includes any future support for +auto-loading configuration files from pre-determined paths. + +Conflicts between configuration files and explicit arguments are handled +exactly like conflicts in the same command line invocation. That is, this +command: + + RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo --case-sensitive + +is exactly equivalent to + + rg --smart-case foo --case-sensitive + +in which case, the --case-sensitive flag would override the --smart-case flag. + ### Shell completions Shell completion files are included in the release tarball for Bash, Fish, Zsh diff --git a/build.rs b/build.rs index 3c16b532a..01b3d18ef 100644 --- a/build.rs +++ b/build.rs @@ -5,6 +5,7 @@ extern crate lazy_static; use std::env; use std::fs; +use std::process; use clap::Shell; @@ -13,14 +14,34 @@ use clap::Shell; mod app; fn main() { + // OUT_DIR is set by Cargo and it's where any additional build artifacts + // are written. let outdir = match env::var_os("OUT_DIR") { - None => return, Some(outdir) => outdir, + None => { + eprintln!( + "OUT_DIR environment variable not defined. \ + Please file a bug: \ + https://github.com/BurntSushi/ripgrep/issues/new"); + process::exit(1); + } }; fs::create_dir_all(&outdir).unwrap(); + // Use clap to build completion files. let mut app = app::app(); app.gen_completions("rg", Shell::Bash, &outdir); app.gen_completions("rg", Shell::Fish, &outdir); app.gen_completions("rg", Shell::PowerShell, &outdir); + // Note that we do not use clap's support for zsh. Instead, zsh completions + // are manually maintained in `complete/_rg`. + + // Make the current git hash available to the build. + let result = process::Command::new("git") + .args(&["rev-parse", "--short=10", "HEAD"]) + .output(); + if let Ok(output) = result { + let hash = String::from_utf8_lossy(&output.stdout); + println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", hash); + } } diff --git a/complete/_rg b/complete/_rg index 6b62c1691..1074597dc 100644 --- a/complete/_rg +++ b/complete/_rg @@ -54,6 +54,7 @@ _rg() { '(--mmap --no-mmap)--mmap[search using memory maps when possible]' '(-H --with-filename --no-filename)--no-filename[suppress all file names]' "(-p --heading --pretty --vimgrep)--no-heading[don't group matches by file name]" + "--no-config[don't load configuration files]" "(--no-ignore-parent)--no-ignore[don't respect ignore files]" "--no-ignore-parent[don't respect ignore files in parent directories]" "--no-ignore-vcs[don't respect version control ignore files]" diff --git a/doc/rg.1 b/doc/rg.1 index fd562e812..b81124632 100644 --- a/doc/rg.1 +++ b/doc/rg.1 @@ -403,6 +403,17 @@ context related options.) .RS .RE .TP +.B \-\-no\-config +Never read configuration files. +When this flag is present, ripgrep will not respect the +RIPGREP_CONFIG_PATH environment variable. +.RS +.PP +If ripgrep ever grows a feature to automatically read configuration +files in pre\-defined locations, then this flag will also disable that +behavior as well. +.RE +.TP .B \-\-no\-messages Suppress all error messages. .RS @@ -597,6 +608,77 @@ ripgrep. Note that this must be passed to every invocation of rg. .RS .RE +.SH CONFIGURATION FILES +.PP +ripgrep supports reading configuration files that change ripgrep\[aq]s +default behavior. +The format of the configuration file is an "rc" style and is very +simple. +It is defined by two rules: +.IP +.nf +\f[C] +1.\ Every\ line\ is\ a\ shell\ argument,\ after\ trimming\ ASCII\ whitespace. +2.\ Lines\ starting\ with\ \[aq]#\[aq]\ (optionally\ preceded\ by\ any\ amount\ of +\ \ \ ASCII\ whitespace)\ are\ ignored. +\f[] +.fi +.PP +ripgrep will look for a single configuration file if and only if the +RIPGREP_CONFIG_PATH environment variable is set and is non\-empty. +ripgrep will parse shell arguments from this file on startup and will +behave as if the arguments in this file were prepended to any explicit +arguments given to ripgrep on the command line. +.PP +For example, if your ripgreprc file contained a single line: +.IP +.nf +\f[C] +\-\-smart\-case +\f[] +.fi +.PP +then the following command +.IP +.nf +\f[C] +RIPGREP_CONFIG_PATH=wherever/.ripgreprc\ rg\ foo +\f[] +.fi +.PP +would behave identically to the following command +.IP +.nf +\f[C] +rg\ \-\-smart\-case\ foo +\f[] +.fi +.PP +ripgrep also provides a flag, \-\-no\-config, that when present will +suppress any and all support for configuration. +This includes any future support for auto\-loading configuration files +from pre\-determined paths. +.PP +Conflicts between configuration files and explicit arguments are handled +exactly like conflicts in the same command line invocation. +That is, this command: +.IP +.nf +\f[C] +RIPGREP_CONFIG_PATH=wherever/.ripgreprc\ rg\ foo\ \-\-case\-sensitive +\f[] +.fi +.PP +is exactly equivalent to +.IP +.nf +\f[C] +rg\ \-\-smart\-case\ foo\ \-\-case\-sensitive +\f[] +.fi +.PP +in which case, the \-\-case\-sensitive flag would override the +\-\-smart\-case flag. .SH SHELL COMPLETION .PP Shell completion files are included in the release tarball for Bash, diff --git a/doc/rg.1.md b/doc/rg.1.md index 6b0542867..c92c6aa3a 100644 --- a/doc/rg.1.md +++ b/doc/rg.1.md @@ -268,6 +268,14 @@ Project home page: https://github.com/BurntSushi/ripgrep when ripgrep thinks it will be faster. (Note that mmap searching doesn't currently support the various context related options.) +--no-config +: Never read configuration files. When this flag is present, ripgrep will not + respect the RIPGREP_CONFIG_PATH environment variable. + + If ripgrep ever grows a feature to automatically read configuration files + in pre-defined locations, then this flag will also disable that behavior as + well. + --no-messages : Suppress all error messages. @@ -392,6 +400,51 @@ Project home page: https://github.com/BurntSushi/ripgrep the default type definitions that are found inside of ripgrep. Note that this must be passed to every invocation of rg. +# CONFIGURATION FILES + +ripgrep supports reading configuration files that change +ripgrep's default behavior. The format of the configuration file is an +"rc" style and is very simple. It is defined by two rules: + + 1. Every line is a shell argument, after trimming ASCII whitespace. + 2. Lines starting with '#' (optionally preceded by any amount of + ASCII whitespace) are ignored. + +ripgrep will look for a single configuration file if and only if the +RIPGREP_CONFIG_PATH environment variable is set and is non-empty. +ripgrep will parse shell arguments from this file on startup and will +behave as if the arguments in this file were prepended to any explicit +arguments given to ripgrep on the command line. + +For example, if your ripgreprc file contained a single line: + + --smart-case + +then the following command + + RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo + +would behave identically to the following command + + rg --smart-case foo + +ripgrep also provides a flag, --no-config, that when present will suppress +any and all support for configuration. This includes any future support +for auto-loading configuration files from pre-determined paths. + +Conflicts between configuration files and explicit arguments are handled +exactly like conflicts in the same command line invocation. That is, +this command: + + RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo --case-sensitive + +is exactly equivalent to + + rg --smart-case foo --case-sensitive + +in which case, the --case-sensitive flag would override the --smart-case +flag. + # SHELL COMPLETION Shell completion files are included in the release tarball for Bash, Fish, Zsh diff --git a/globset/Cargo.toml b/globset/Cargo.toml index 96d47e3fd..145166d15 100644 --- a/globset/Cargo.toml +++ b/globset/Cargo.toml @@ -21,7 +21,7 @@ bench = false [dependencies] aho-corasick = "0.6.0" fnv = "1.0" -log = "0.3" +log = "0.4" memchr = "2" regex = "0.2.1" diff --git a/grep/Cargo.toml b/grep/Cargo.toml index ddce71aee..34f96bc45 100644 --- a/grep/Cargo.toml +++ b/grep/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["regex", "grep", "egrep", "search", "pattern"] license = "Unlicense/MIT" [dependencies] -log = "0.3" +log = "0.4" memchr = "2" regex = "0.2.1" regex-syntax = "0.4.0" diff --git a/ignore/Cargo.toml b/ignore/Cargo.toml index 89d8672c0..7948cd748 100644 --- a/ignore/Cargo.toml +++ b/ignore/Cargo.toml @@ -21,7 +21,7 @@ bench = false crossbeam = "0.3" globset = { version = "0.2.1", path = "../globset" } lazy_static = "1" -log = "0.3" +log = "0.4" memchr = "2" regex = "0.2.1" same-file = "1" diff --git a/src/app.rs b/src/app.rs index f2f084e3d..16cd3d826 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,15 @@ -use std::collections::HashMap; +// This module defines the set of command line arguments that ripgrep supports, +// including some light validation. +// +// This module is purposely written in a bare-bones way, since it is included +// in ripgrep's build.rs file as a way to generate completion files for common +// shells. +// +// The only other place that ripgrep deals with clap is in src/args.rs, which +// is where we read clap's configuration from the end user's arguments and turn +// it into a ripgrep-specific configuration type that is not coupled with clap. -use clap::{App, AppSettings, Arg, ArgSettings}; +use clap::{self, App, AppSettings}; const ABOUT: &str = " ripgrep (rg) recursively searches your current directory for a regex pattern. @@ -13,6 +22,11 @@ Note that ripgrep may abort unexpectedly when using default settings if it searches a file that is simultaneously truncated. This behavior can be avoided by passing the --no-mmap flag. +ripgrep supports configuration files. Set RIPGREP_CONFIG_PATH to a +configuration file. The file can specify one shell argument per line. Lines +starting with '#' are ignored. For more details, see the man page or the +README. + Project home page: https://github.com/BurntSushi/ripgrep Use -h for short descriptions and --help for more details."; @@ -37,21 +51,16 @@ OPTIONS: {unified}"; /// Build a clap application parameterized by usage strings. -/// -/// The function given should take a clap argument name and return a help -/// string. `app` will panic if a usage string is not defined. -/// -/// This is an intentionally stand-alone module so that it can be used easily -/// in a `build.rs` script to build shell completion files. pub fn app() -> App<'static, 'static> { - let arg = |name| { - Arg::with_name(name) - .help(USAGES[name].short) - .long_help(USAGES[name].long) - }; - let flag = |name| arg(name).long(name); + // We need to specify our version in a static because we've painted clap + // into a corner. We've told it that every string we give it will be + // 'static, but we need to build the version string dynamically. We can + // fake the 'static lifetime with lazy_static. + lazy_static! { + static ref LONG_VERSION: String = long_version(); + } - App::new("ripgrep") + let mut app = App::new("ripgrep") .author(crate_authors!()) .version(crate_version!()) .long_version(LONG_VERSION.as_str()) @@ -60,560 +69,1475 @@ pub fn app() -> App<'static, 'static> { .setting(AppSettings::UnifiedHelpMessage) .usage(USAGE) .template(TEMPLATE) - .help_message("Prints help information. Use --help for more details.") - // First, set up primary positional/flag arguments. - .arg(arg("PATTERN") - .required_unless_one(&[ - "file", "files", "help-short", "help", "regexp", "type-list", - "ripgrep-version", - ])) - .arg(arg("PATH").multiple(true)) - .arg(flag("regexp").short("e") - .takes_value(true).multiple(true).number_of_values(1) - .set(ArgSettings::AllowLeadingHyphen) - .value_name("PATTERN")) - .arg(flag("files") - // This should also conflict with `PATTERN`, but the first file - // path will actually be in `PATTERN`. - .conflicts_with_all(&["file", "regexp", "type-list"])) - .arg(flag("type-list") - .conflicts_with_all(&["file", "files", "PATTERN", "regexp"])) - // Second, set up common flags. - .arg(flag("text").short("a")) - .arg(flag("count").short("c")) - .arg(flag("color") - .value_name("WHEN") - .takes_value(true) - .hide_possible_values(true) - .possible_values(&["never", "auto", "always", "ansi"]) - .default_value_if("vimgrep", None, "never")) - .arg(flag("colors").value_name("SPEC") - .takes_value(true).multiple(true).number_of_values(1)) - .arg(flag("encoding").short("E").value_name("ENCODING") - .takes_value(true).number_of_values(1)) - .arg(flag("fixed-strings").short("F")) - .arg(flag("glob").short("g") - .takes_value(true).multiple(true).number_of_values(1) - .set(ArgSettings::AllowLeadingHyphen) - .value_name("GLOB")) - .arg(flag("iglob") - .takes_value(true).multiple(true).number_of_values(1) - .set(ArgSettings::AllowLeadingHyphen) - .value_name("GLOB")) - .arg(flag("ignore-case").short("i")) - .arg(flag("line-number").short("n")) - .arg(flag("line-number-width") - .value_name("NUM").takes_value(true) - .validator(validate_line_number_width)) - .arg(flag("no-line-number").short("N").overrides_with("line-number")) - .arg(flag("quiet").short("q")) - .arg(flag("type").short("t") - .takes_value(true).multiple(true).number_of_values(1) - .value_name("TYPE")) - .arg(flag("type-not").short("T") - .takes_value(true).multiple(true).number_of_values(1) - .value_name("TYPE")) - .arg(flag("unrestricted").short("u") - .multiple(true)) - .arg(flag("invert-match").short("v")) - .arg(flag("word-regexp").short("w").overrides_with("line-regexp")) - .arg(flag("line-regexp").short("x")) - // Third, set up less common flags. - .arg(flag("after-context").short("A") - .value_name("NUM").takes_value(true) - .validator(validate_number)) - .arg(flag("before-context").short("B") - .value_name("NUM").takes_value(true) - .validator(validate_number)) - .arg(flag("context").short("C") - .value_name("NUM").takes_value(true) - .validator(validate_number)) - .arg(flag("column")) - .arg(flag("context-separator") - .value_name("SEPARATOR").takes_value(true)) - .arg(flag("dfa-size-limit") - .value_name("NUM+SUFFIX?").takes_value(true)) - .arg(flag("debug")) - .arg(flag("file").short("f") - .value_name("FILE").takes_value(true) - .set(ArgSettings::AllowLeadingHyphen) - .multiple(true).number_of_values(1)) - .arg(flag("files-with-matches").short("l")) - .arg(flag("files-without-match")) - .arg(flag("with-filename").short("H")) - .arg(flag("no-filename").overrides_with("with-filename")) - .arg(flag("heading")) - .arg(flag("no-heading").overrides_with("heading")) - .arg(flag("hidden")) - .arg(flag("ignore-file") - .value_name("FILE").takes_value(true) - .set(ArgSettings::AllowLeadingHyphen) - .multiple(true).number_of_values(1)) - .arg(flag("follow").short("L")) - .arg(flag("max-count") - .short("m").value_name("NUM").takes_value(true) - .validator(validate_number)) - .arg(flag("max-filesize") - .value_name("NUM+SUFFIX?").takes_value(true)) - .arg(flag("maxdepth") - .value_name("NUM").takes_value(true) - .validator(validate_number)) - .arg(flag("mmap")) - .arg(flag("no-messages")) - .arg(flag("no-mmap")) - .arg(flag("no-ignore")) - .arg(flag("no-ignore-parent")) - .arg(flag("no-ignore-vcs")) - .arg(flag("null").short("0")) - .arg(flag("only-matching").short("o")) - .arg(flag("passthru").alias("passthrough") - .conflicts_with_all(&["only-matching", "replace"])) - .arg(flag("path-separator").value_name("SEPARATOR").takes_value(true)) - .arg(flag("pretty").short("p")) - .arg(flag("replace").short("r") - .set(ArgSettings::AllowLeadingHyphen) - .value_name("ARG").takes_value(true)) - .arg(flag("regex-size-limit") - .value_name("NUM+SUFFIX?").takes_value(true)) - .arg(flag("case-sensitive").short("s")) - .arg(flag("smart-case").short("S")) - .arg(flag("sort-files")) - .arg(flag("threads") - .short("j").value_name("ARG").takes_value(true) - .validator(validate_number)) - .arg(flag("vimgrep").overrides_with("count")) - .arg(flag("max-columns").short("M") - .value_name("NUM").takes_value(true) - .validator(validate_number)) - .arg(flag("type-add") - .value_name("TYPE").takes_value(true) - .multiple(true).number_of_values(1)) - .arg(flag("type-clear") - .value_name("TYPE").takes_value(true) - .multiple(true).number_of_values(1)) - .arg(flag("search-zip").short("z")) -} - -struct Usage { - short: &'static str, - long: &'static str, -} - -macro_rules! doc { - ($map:expr, $name:expr, $short:expr) => { - doc!($map, $name, $short, $short) - }; - ($map:expr, $name:expr, $short:expr, $long:expr) => { - $map.insert($name, Usage { - short: $short, - long: concat!($long, "\n "), - }); + .help_message("Prints help information. Use --help for more details."); + for arg in all_args_and_flags() { + app = app.arg(arg.claparg); + } + app +} + +/// Return the "long" format of ripgrep's version string. +fn long_version() -> String { + // Let's say whether faster CPU instructions are enabled or not. + let mut features = vec![]; + if cfg!(feature = "simd-accel") { + features.push("+SIMD"); + } else { + features.push("-SIMD"); + } + if cfg!(feature = "avx-accel") { + features.push("+AVX"); + } else { + features.push("-AVX"); + } + // Do we have a git hash? + // (Yes, if ripgrep was built on a machine with `git` installed.) + let hash = match option_env!("RIPGREP_BUILD_GIT_HASH") { + None => String::new(), + Some(githash) => format!(" (rev {})", githash), }; + // Put everything together. + format!("{}{}\n{}", crate_version!(), hash, features.join(" ")) +} + +/// Arg is a light alias for a clap::Arg that is specialized to compile time +/// string literals. +type Arg = clap::Arg<'static, 'static>; + +/// RGArg is a light wrapper around a clap::Arg and also contains some metadata +/// about the underlying Arg so that it can be inspected for other purposes +/// (e.g., hopefully generating a man page). +/// +/// Note that this type is purposely overly constrained to ripgrep's particular +/// use of clap. +#[allow(dead_code)] +#[derive(Clone)] +struct RGArg { + /// The underlying clap argument. + claparg: Arg, + /// The name of this argument. This is always present and is the name + /// used in the code to find the value of an argument at runtime. + name: &'static str, + /// A short documentation string describing this argument. This string + /// should fit on a single line and be a complete sentence. + /// + /// This is shown in the `-h` output. + doc_short: &'static str, + /// A longer documentation string describing this argument. This usually + /// starts with the contents of `doc_short`. This is also usually many + /// lines, potentially paragraphs, and may contain examples and additional + /// prose. + /// + /// This is shown in the `--help` output. + doc_long: &'static str, + /// The type of this argument. + kind: RGArgKind, } -lazy_static! { - static ref LONG_VERSION: String = { - let mut features: Vec<&str> = vec![]; +/// The kind of a ripgrep argument. +/// +/// This can be one of three possibilities: a positional argument, a boolean +/// switch flag or a flag that accepts exactly one argument. Each variant +/// stores argument type specific data. +/// +/// Note that clap supports more types of arguments than this, but we don't +/// (and probably shouldn't) use them in ripgrep. +/// +/// Finally, note that we don't capture *all* state about an argument in this +/// type. Some state is only known to clap. There isn't any particular reason +/// why; the state we do capture is motivated by use cases (like generating +/// documentation). +#[derive(Clone)] +enum RGArgKind { + /// A positional argument. + Positional { + /// The name of the value used in the `-h/--help` output. By + /// convention, this is an all-uppercase string. e.g., `PATH` or + /// `PATTERN`. + value_name: &'static str, + /// Whether an argument can be repeated multiple times or not. + /// + /// The only argument this applies to is PATH, where an end user can + /// specify multiple paths for ripgrep to search. + /// + /// If this is disabled, then an argument can only be provided once. + /// For example, PATTERN is one such argument. (Note that the + /// -e/--regexp flag is distinct from the positional PATTERN argument, + /// and it can be provided multiple times.) + multiple: bool, + }, + /// A boolean switch. + Switch { + /// The long name of a flag. This is always non-empty. + long: &'static str, + /// The short name of a flag. This is empty if a flag only has a long + /// name. + short: Option<&'static str>, + /// Whether this switch can be provided multiple times where meaning + /// is attached to the number of times this flag is given. + /// + /// Note that every switch can be provided multiple times. This + /// particular state indicates whether all instances of a switch are + /// relevant or not. + /// + /// For example, the -u/--unrestricted flag can be provided multiple + /// times where each repeated use of it indicates more relaxing of + /// ripgrep's filtering. Conversely, the -i/--ignore-case flag can + /// also be provided multiple times, but it is simply considered either + /// present or not. In these cases, -u/--unrestricted has `multiple` + /// set to `true` while -i/--ignore-case has `multiple` set to `false`. + multiple: bool, + }, + /// A flag the accepts a single value. + Flag { + /// The long name of a flag. This is always non-empty. + long: &'static str, + /// The short name of a flag. This is empty if a flag only has a long + /// name. + short: Option<&'static str>, + /// The name of the value used in the `-h/--help` output. By + /// convention, this is an all-uppercase string. e.g., `PATH` or + /// `PATTERN`. + value_name: &'static str, + /// Whether this flag can be provided multiple times with multiple + /// distinct values. + /// + /// Note that every flag can be provided multiple times. This + /// particular state indicates whether all instances of a flag are + /// relevant or not. + /// + /// For example, the -g/--glob flag can be provided multiple times and + /// all of its values should be interpreted by ripgrep. Conversely, + /// while the -C/--context flag can also be provided multiple times, + /// only its last instance is used while all previous instances are + /// ignored. In these cases, -g/--glob has `multiple` set to `true` + /// while -C/--context has `multiple` set to `false`. + multiple: bool, + /// A set of possible values for this flag. If an end user provides + /// any value other than what's in this set, then clap will report an + /// error. + possible_values: Vec<&'static str>, + } +} - if cfg!(feature = "avx-accel") { - features.push("+AVX"); - } else { - features.push("-AVX"); +impl RGArg { + /// Create a positional argument. + /// + /// The `long_name` parameter is the name of the argument, e.g., `pattern`. + /// The `value_name` parameter is a name that describes the type of + /// argument this flag accepts. It should be in uppercase, e.g., PATH or + /// PATTERN. + fn positional(name: &'static str, value_name: &'static str) -> RGArg { + RGArg { + claparg: Arg::with_name(name).value_name(value_name), + name: name, + doc_short: "", + doc_long: "", + kind: RGArgKind::Positional { + value_name: value_name, + multiple: false, + }, } + } - if cfg!(feature = "simd-accel") { - features.push("+SIMD"); - } else { - features.push("-SIMD"); + /// Create a boolean switch. + /// + /// The `long_name` parameter is the name of the flag, e.g., `--long-name`. + /// + /// All switches may be repeated an arbitrary number of times. If a switch + /// is truly boolean, that consumers of clap's configuration should only + /// check whether the flag is present or not. Otherwise, consumers may + /// inspect the number of times the switch is used. + fn switch(long_name: &'static str) -> RGArg { + let claparg = Arg::with_name(long_name) + .long(long_name) + .multiple(true); + RGArg { + claparg: claparg, + name: long_name, + doc_short: "", + doc_long: "", + kind: RGArgKind::Switch { + long: long_name, + short: None, + multiple: false, + }, } + } - format!("{}\n{}", crate_version!(), features.join(" ")) - }; + /// Create a flag. A flag always accepts exactly one argument. + /// + /// The `long_name` parameter is the name of the flag, e.g., `--long-name`. + /// The `value_name` parameter is a name that describes the type of + /// argument this flag accepts. It should be in uppercase, e.g., PATH or + /// PATTERN. + /// + /// All flags may be repeated an arbitrary number of times. If a flag has + /// only one logical value, that consumers of clap's configuration should + /// only use the last value. + fn flag(long_name: &'static str, value_name: &'static str) -> RGArg { + let claparg = Arg::with_name(long_name) + .long(long_name) + .value_name(value_name) + .takes_value(true) + .multiple(true) + .number_of_values(1); + RGArg { + claparg: claparg, + name: long_name, + doc_short: "", + doc_long: "", + kind: RGArgKind::Flag { + long: long_name, + short: None, + value_name: value_name, + multiple: false, + possible_values: vec![], + } + } + } - static ref USAGES: HashMap<&'static str, Usage> = { - let mut h = HashMap::new(); - doc!(h, "help-short", - "Show short help output.", - "Show short help output. Use --help to show more details."); - doc!(h, "help", - "Show verbose help output.", - "When given, more details about flags are provided."); - doc!(h, "ripgrep-version", - "Prints version information."); - - doc!(h, "PATTERN", - "A regular expression used for searching.", - "A regular expression used for searching. To match a pattern \ - beginning with a dash, use the -e/--regexp option."); - doc!(h, "regexp", - "Use pattern to search.", - "Use pattern to search. This option can be provided multiple \ - times, where all patterns given are searched. This is also \ - useful when searching for patterns that start with a dash."); - doc!(h, "PATH", - "A file or directory to search.", - "A file or directory to search. Directories are searched \ - recursively. Paths specified on the command line override glob \ - and ignore rules."); - doc!(h, "files", - "Print each file that would be searched.", - "Print each file that would be searched without actually \ - performing the search. This is useful to determine whether a \ - particular file is being searched or not."); - doc!(h, "type-list", - "Show all supported file types.", - "Show all supported file types and their corresponding globs."); - - doc!(h, "text", - "Search binary files as if they were text."); - doc!(h, "count", - "Only show count of matches for each file."); - doc!(h, "color", - "When to use color. [default: auto]", - "When to use color in the output. The possible values are never, \ - auto, always or ansi. The default is auto. When always is used, \ - coloring is attempted based on your environment. When ansi is \ - used, coloring is forcefully done using ANSI escape color \ - codes."); - doc!(h, "colors", - "Configure color settings and styles.", - "This flag specifies color settings for use in the output. \ - This flag may be provided multiple times. Settings are applied \ - iteratively. Colors are limited to one of eight choices: \ - red, blue, green, cyan, magenta, yellow, white and black. \ - Styles are limited to nobold, bold, nointense or intense.\n\n\ - The format of the flag is {type}:{attribute}:{value}. {type} \ - should be one of path, line, column or match. {attribute} can \ - be fg, bg or style. {value} is either a color (for fg and bg) \ - or a text style. A special format, {type}:none, will clear all \ - color settings for {type}.\n\nFor example, the following \ - command will change the match color to magenta and the \ - background color for line numbers to yellow:\n\n\ - rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.\n\n\ - Extended colors can be used for {value} when the terminal \ - supports ANSI color sequences. These are specified as either \ - 'x' (256-color) or 'x,x,x' (24-bit truecolor) where x is a \ - number between 0 and 255 inclusive. \n\nFor example, the \ - following command will change the match background color to that \ - represented by the rgb value (0,128,255):\n\n\ - rg --colors 'match:bg:0,128,255'\n\nNote that the the intense \ - and nointense style flags will have no effect when used \ - alongside these extended color codes."); - doc!(h, "encoding", - "Specify the text encoding of files to search.", - "Specify the text encoding that ripgrep will use on all files \ - searched. The default value is 'auto', which will cause ripgrep \ - to do a best effort automatic detection of encoding on a \ - per-file basis. Other supported values can be found in the list \ - of labels here: \ - https://encoding.spec.whatwg.org/#concept-encoding-get"); - doc!(h, "fixed-strings", - "Treat the pattern as a literal string.", - "Treat the pattern as a literal string instead of a regular \ - expression. When this flag is used, special regular expression \ - meta characters such as (){}*+. do not need to be escaped."); - doc!(h, "glob", - "Include or exclude files/directories.", - "Include or exclude files/directories for searching that \ - match the given glob. This always overrides any other \ - ignore logic. Multiple glob flags may be used. Globbing \ - rules match .gitignore globs. Precede a glob with a ! \ - to exclude it."); - doc!(h, "iglob", - "Include or exclude files/directories case insensitively.", - "Include or exclude files/directories for searching that \ - match the given glob. This always overrides any other \ - ignore logic. Multiple glob flags may be used. Globbing \ - rules match .gitignore globs. Precede a glob with a ! \ - to exclude it. Globs are matched case insensitively."); - doc!(h, "ignore-case", - "Case insensitive search.", - "Case insensitive search. This is overridden by \ - --case-sensitive."); - doc!(h, "line-number", - "Show line numbers.", - "Show line numbers (1-based). This is enabled by default when \ - searching in a tty."); - doc!(h, "line-number-width", - "Left pad line numbers upto NUM width.", - "Left pad line numbers upto NUM width. Space is used as \ - the default padding character. This has no effect if \ - --no-line-number is enabled."); - doc!(h, "no-line-number", - "Suppress line numbers.", - "Suppress line numbers. This is enabled by default when NOT \ - searching in a tty."); - doc!(h, "quiet", - "Do not print anything to stdout.", - "Do not print anything to stdout. If a match is found in a file, \ - stop searching. This is useful when ripgrep is used only for \ - its exit code."); - doc!(h, "type", - "Only search files matching TYPE.", - "Only search files matching TYPE. Multiple type flags may be \ - provided. Use the --type-list flag to list all available \ - types."); - doc!(h, "type-not", - "Do not search files matching TYPE.", - "Do not search files matching TYPE. Multiple type-not flags may \ - be provided. Use the --type-list flag to list all available \ - types."); - doc!(h, "unrestricted", - "Reduce the level of \"smart\" searching.", - "Reduce the level of \"smart\" searching. A single -u \ - won't respect .gitignore (etc.) files. Two -u flags will \ - additionally search hidden files and directories. Three \ - -u flags will additionally search binary files. -uu is \ - roughly equivalent to grep -r and -uuu is roughly \ - equivalent to grep -a -r."); - doc!(h, "invert-match", - "Invert matching.", - "Invert matching. Show lines that don't match given patterns."); - doc!(h, "word-regexp", - "Only show matches surrounded by word boundaries.", - "Only show matches surrounded by word boundaries. This is \ - equivalent to putting \\b before and after all of the search \ - patterns."); - doc!(h, "line-regexp", - "Only show matches surrounded by line boundaries.", - "Only show matches surrounded by line boundaries. This is \ - equivalent to putting ^...$ around all of the search patterns."); - - doc!(h, "after-context", - "Show NUM lines after each match."); - doc!(h, "before-context", - "Show NUM lines before each match."); - doc!(h, "context", - "Show NUM lines before and after each match."); - doc!(h, "column", - "Show column numbers", - "Show column numbers (1-based). This only shows the column \ - numbers for the first match on each line. This does not try \ - to account for Unicode. One byte is equal to one column. This \ - implies --line-number."); - doc!(h, "context-separator", - "Set the context separator string. [default: --]", - "The string used to separate non-contiguous context lines in the \ - output. Escape sequences like \\x7F or \\t may be used. The \ - default value is --."); - doc!(h, "debug", - "Show debug messages.", - "Show debug messages. Please use this when filing a bug report."); - doc!(h, "dfa-size-limit", - "The upper size limit of the generated dfa.", - "The upper size limit of the generated dfa. The default limit is \ - 10M. This should only be changed on very large regex inputs \ - where the (slower) fallback regex engine may otherwise be used. \ - \n\nThe argument accepts the same size suffixes as allowed in \ - the 'max-filesize' argument."); - doc!(h, "file", - "Search for patterns from the given file.", - "Search for patterns from the given file, with one pattern per \ - line. When this flag is used or multiple times or in \ - combination with the -e/--regexp flag, then all patterns \ - provided are searched. Empty pattern lines will match all input \ - lines, and the newline is not counted as part of the pattern."); - doc!(h, "files-with-matches", - "Only show the paths with at least one match."); - doc!(h, "files-without-match", - "Only show the paths that contains zero matches."); - doc!(h, "with-filename", - "Show file name for each match.", - "Display the file name for matches. This is the default when \ - more than one file is searched. If --heading is enabled, the \ - file name will be shown above clusters of matches from each \ - file; otherwise, the file name will be shown on each match."); - doc!(h, "no-filename", - "Never show the file name for a match.", - "Never show the file name for a match. This is the default when \ - one file is searched."); - doc!(h, "heading", - "Show matches grouped by each file.", - "This shows the file name above clusters of matches from each \ - file instead of showing the file name for every match. This is \ - the default mode at a tty."); - doc!(h, "no-heading", - "Don't group matches by each file.", - "Don't group matches by each file. If -H/--with-filename is \ - enabled, then file names will be shown for every line matched. \ - This is the default mode when not at a tty."); - doc!(h, "hidden", - "Search hidden files and directories.", - "Search hidden files and directories. By default, hidden files \ - and directories are skipped."); - doc!(h, "ignore-file", - "Specify additional ignore files.", - "Specify one or more files which contain ignore patterns. \ - These patterns are applied after the patterns found in \ - .gitignore and .ignore are applied. Ignore patterns should \ - be in the gitignore format and are matched relative to the \ - current working directory. Multiple additional ignore files \ - can be specified by using the --ignore-file flag several times. \ - When specifying multiple ignore files, earlier files have lower \ - precedence than later files. If you are looking for a way to \ - include or exclude files and directories directly used -g \ - instead."); - doc!(h, "follow", - "Follow symbolic links."); - doc!(h, "max-count", - "Limit the number of matches.", - "Limit the number of matching lines per file searched to NUM."); - doc!(h, "max-filesize", - "Ignore files larger than NUM in size.", - "Ignore files larger than NUM in size. Does not ignore \ - directories. \ - \n\nThe input format accepts suffixes of K, M or G which \ - correspond to kilobytes, megabytes and gigabytes. If no suffix \ - is provided the input is treated as bytes. \ - \n\nExample: --max-filesize 50K or --max-filesize 80M"); - doc!(h, "maxdepth", - "Descend at most NUM directories.", - "Limit the depth of directory traversal to NUM levels beyond \ - the paths given. A value of zero only searches the \ - starting-points themselves.\n\nFor example, \ - 'rg --maxdepth 0 dir/' is a no-op because dir/ will not be \ - descended into. 'rg --maxdepth 1 dir/' will search only the \ - direct children of dir/."); - doc!(h, "mmap", - "Searching using memory maps when possible.", - "Search using memory maps when possible. This is enabled by \ - default when ripgrep thinks it will be faster. Note that memory \ - map searching doesn't currently support all options, so if an \ - incompatible option (e.g., --context) is given with --mmap, \ - then memory maps will not be used."); - doc!(h, "no-messages", - "Suppress all error messages.", - "Suppress all error messages. This is equivalent to redirecting \ - stderr to /dev/null."); - doc!(h, "no-mmap", - "Never use memory maps.", - "Never use memory maps, even when they might be faster."); - doc!(h, "no-ignore", - "Don't respect ignore files.", - "Don't respect ignore files (.gitignore, .ignore, etc.). This \ - implies --no-ignore-parent and --no-ignore-vcs."); - doc!(h, "no-ignore-parent", - "Don't respect ignore files in parent directories.", - "Don't respect ignore files (.gitignore, .ignore, etc.) in \ - parent directories."); - doc!(h, "no-ignore-vcs", - "Don't respect VCS ignore files", - "Don't respect version control ignore files (.gitignore, etc.). \ - This implies --no-ignore-parent. Note that .ignore files will \ - continue to be respected."); - doc!(h, "null", - "Print NUL byte after file names", - "Whenever a file name is printed, follow it with a NUL byte. \ - This includes printing file names before matches, and when \ - printing a list of matching files such as with --count, \ - --files-with-matches and --files. This option is useful for use \ - with xargs."); - doc!(h, "only-matching", - "Print only matched parts of a line.", - "Print only the matched (non-empty) parts of a matching line, \ - with each such part on a separate output line."); - doc!(h, "passthru", - "Show both matching and non-matching lines."); - doc!(h, "path-separator", - "Path separator to use when printing file paths.", - "The path separator to use when printing file paths. This \ - defaults to your platform's path separator, which is / on Unix \ - and \\ on Windows. This flag is intended for overriding the \ - default when the environment demands it (e.g., cygwin). A path \ - separator is limited to a single byte."); - doc!(h, "pretty", - "Alias for --color always --heading --line-number."); - doc!(h, "replace", - "Replace matches with string given.", - "Replace every match with the string given when printing \ - results. Neither this flag nor any other flag will modify your \ - files.\n\nCapture group indices (e.g., $5) and names \ - (e.g., $foo) are supported in the replacement string.\n\n\ - Note that the replacement by default replaces each match, and \ - NOT the entire line. To replace the entire line, you should \ - match the entire line."); - doc!(h, "regex-size-limit", - "The upper size limit of the compiled regex.", - "The upper size limit of the compiled regex. The default limit \ - is 10M. \n\nThe argument accepts the same size suffixes as \ - allowed in the 'max-filesize' argument."); - doc!(h, "case-sensitive", - "Search case sensitively (default).", - "Search case sensitively. This overrides -i/--ignore-case and \ - -S/--smart-case."); - doc!(h, "smart-case", - "Smart case search.", - "Searches case insensitively if the pattern is all lowercase. \ - Search case sensitively otherwise. This is overridden by \ - either -s/--case-sensitive or -i/--ignore-case."); - doc!(h, "sort-files", - "Sort results by file path. Implies --threads=1.", - "Sort results by file path. Note that this currently \ - disables all parallelism and runs search in a single thread."); - doc!(h, "threads", - "The approximate number of threads to use.", - "The approximate number of threads to use. A value of 0 (which \ - is the default) causes ripgrep to choose the thread count \ - using heuristics."); - doc!(h, "vimgrep", - "Show results in vim compatible format.", - "Show results with every match on its own line, including \ - line numbers and column numbers. With this option, a line with \ - more than one match will be printed more than once."); - doc!(h, "max-columns", - "Don't print lines longer than this limit in bytes.", - "Don't print lines longer than this limit in bytes. Longer lines \ - are omitted, and only the number of matches in that line is \ - printed."); - - doc!(h, "type-add", - "Add a new glob for a file type.", - "Add a new glob for a particular file type. Only one glob can be \ - added at a time. Multiple --type-add flags can be provided. \ - Unless --type-clear is used, globs are added to any existing \ - globs defined inside of ripgrep.\n\nNote that this MUST be \ - passed to every invocation of ripgrep. Type settings are NOT \ - persisted.\n\nExample: \ - rg --type-add 'foo:*.foo' -tfoo PATTERN.\n\n\ - --type-add can also be used to include rules from other types \ - with the special include directive. The include directive \ - permits specifying one or more other type names (separated by a \ - comma) that have been defined and its rules will automatically \ - be imported into the type specified. For example, to create a \ - type called src that matches C++, Python and Markdown files, \ - one can use:\n\n\ - --type-add 'src:include:cpp,py,md'\n\n\ - Additional glob rules can still be added to the src type by \ - using the --type-add flag again:\n\n\ - --type-add 'src:include:cpp,py,md' --type-add 'src:*.foo'\n\n\ - Note that type names must consist only of Unicode letters or \ - numbers. Punctuation characters are not allowed."); - doc!(h, "type-clear", - "Clear globs for given file type.", - "Clear the file type globs previously defined for TYPE. This \ - only clears the default type definitions that are found inside \ - of ripgrep.\n\nNote that this MUST be passed to every \ - invocation of ripgrep. Type settings are NOT persisted."); - doc!(h, "search-zip", - "Search in compressed files.", - "Search in compressed files. Currently gz, bz2, xz, and \ - lzma files are supported. This option expects the decompression \ - binaries to be available in the system PATH."); - - h - }; -} + /// Set the short flag name. + /// + /// This panics if this arg isn't a switch or a flag. + fn short(mut self, name: &'static str) -> RGArg { + match self.kind { + RGArgKind::Positional{..} => panic!("expected switch or flag"), + RGArgKind::Switch { ref mut short, .. } => { + *short = Some(name); + } + RGArgKind::Flag { ref mut short, .. } => { + *short = Some(name); + } + } + self.claparg = self.claparg.short(name); + self + } -fn validate_line_number_width(s: String) -> Result<(), String> { - if s.starts_with("0") { - Err(String::from( - "Custom padding characters are currently not supported. \ - Please enter only a numeric value.")) - } else { - validate_number(s) + /// Set the "short" help text. + /// + /// This should be a single line. It is shown in the `-h` output. + fn help(mut self, text: &'static str) -> RGArg { + self.doc_short = text; + self.claparg = self.claparg.help(text); + self + } + + /// Set the "long" help text. + /// + /// This should be at least a single line, usually longer. It is shown in + /// the `--help` output. + fn long_help(mut self, text: &'static str) -> RGArg { + self.doc_long = text; + self.claparg = self.claparg.long_help(text); + self + } + + /// Enable this argument to accept multiple values. + /// + /// Note that while switches and flags can always be repeated an arbitrary + /// number of times, this particular method enables the flag to be + /// logically repeated where each occurrence of the flag may have + /// significance. That is, when this is disabled, then a switch is either + /// present or not and a flag has exactly one value (the last one given). + /// When this is enabled, then a switch has a count corresponding to the + /// number of times it is used and a flag's value is a list of all values + /// given. + /// + /// For the most part, this distinction is resolved by consumers of clap's + /// configuration. + fn multiple(mut self) -> RGArg { + // Why not put `multiple` on RGArg proper? Because it's useful to + // document it distinct for each different kind. See RGArgKind docs. + match self.kind { + RGArgKind::Positional { ref mut multiple, .. } => { + self.claparg = self.claparg.multiple(true); + *multiple = true; + } + // We don't need to modify clap's state in the following cases + // because all switches and flags always have `multiple` enabled. + RGArgKind::Switch { ref mut multiple, .. } => { + *multiple = true; + } + RGArgKind::Flag { ref mut multiple, .. } => { + *multiple = true; + } + } + self + } + + /// Set the possible values for this argument. If this argument is not + /// a flag, then this panics. + /// + /// If the end user provides any value other than what is given here, then + /// clap will report an error to the user. + /// + /// Note that this will suppress clap's automatic output of possible values + /// when using -h/--help, so users of this method should provide + /// appropriate documentation for the choices in the "long" help text. + fn possible_values(mut self, values: &[&'static str]) -> RGArg { + match self.kind { + RGArgKind::Positional{..} => panic!("expected flag"), + RGArgKind::Switch{..} => panic!("expected flag"), + RGArgKind::Flag { ref mut possible_values, .. } => { + *possible_values = values.to_vec(); + self.claparg = self.claparg + .possible_values(values) + .hide_possible_values(true); + } + } + self + } + + /// Add an alias to this argument. + /// + /// Aliases are not show in the output of -h/--help. + fn alias(mut self, name: &'static str) -> RGArg { + self.claparg = self.claparg.alias(name); + self + } + + /// Permit this flag to have values that begin with a hypen. + /// + /// This panics if this arg is not a flag. + fn allow_leading_hyphen(mut self) -> RGArg { + match self.kind { + RGArgKind::Positional{..} => panic!("expected flag"), + RGArgKind::Switch{..} => panic!("expected flag"), + RGArgKind::Flag {..} => { + self.claparg = self.claparg.allow_hyphen_values(true); + } + } + self + } + + /// Sets this argument to a required argument, unless one of the given + /// arguments is provided. + fn required_unless(mut self, names: &[&'static str]) -> RGArg { + self.claparg = self.claparg.required_unless_one(names); + self } + + /// Sets conflicting arguments. That is, if this argument is used whenever + /// any of the other arguments given here are used, then clap will report + /// an error. + fn conflicts(mut self, names: &[&'static str]) -> RGArg { + self.claparg = self.claparg.conflicts_with_all(names); + self + } + + /// Sets an overriding argument. That is, if this argument and the given + /// argument are both provided by an end user, then the "last" one will + /// win. ripgrep will behave as if any previous instantiations did not + /// happen. + fn overrides(mut self, name: &'static str) -> RGArg { + self.claparg = self.claparg.overrides_with(name); + self + } + + /// Sets the default value of this argument if and only if the argument + /// given is present. + fn default_value_if( + mut self, + value: &'static str, + arg_name: &'static str, + ) -> RGArg { + self.claparg = self.claparg.default_value_if(arg_name, None, value); + self + } + + /// Indicate that any value given to this argument should be a number. If + /// it's not a number, then clap will report an error to the end user. + fn number(mut self) -> RGArg { + self.claparg = self.claparg.validator(|val| { + val.parse::().map(|_| ()).map_err(|err| err.to_string()) + }); + self + } + + /// Indicate that any value given to this argument should be a valid + /// line number width. A valid line number width cannot start with `0` + /// to maintain compatibility with future improvements that add support + /// for padding character specifies. + fn line_number_width(mut self) -> RGArg { + self.claparg = self.claparg.validator(|val| { + if val.starts_with("0") { + Err(String::from( + "Custom padding characters are currently not supported. \ + Please enter only a numeric value.")) + } else { + val.parse::().map(|_| ()).map_err(|err| err.to_string()) + } + }); + self + } +} + +// We add an extra space to long descriptions so that a black line is inserted +// between flag descriptions in --help output. +macro_rules! long { + ($lit:expr) => { concat!($lit, " ") } +} + +fn all_args_and_flags() -> Vec { + let mut args = vec![]; + // The positional arguments must be defined first and in order. + arg_pattern(&mut args); + arg_path(&mut args); + // Flags can be defined in any order, but we do it alphabetically. + flag_after_context(&mut args); + flag_before_context(&mut args); + flag_case_sensitive(&mut args); + flag_color(&mut args); + flag_colors(&mut args); + flag_column(&mut args); + flag_context(&mut args); + flag_context_separator(&mut args); + flag_count(&mut args); + flag_debug(&mut args); + flag_dfa_size_limit(&mut args); + flag_encoding(&mut args); + flag_file(&mut args); + flag_files(&mut args); + flag_files_with_matches(&mut args); + flag_files_without_match(&mut args); + flag_fixed_strings(&mut args); + flag_follow(&mut args); + flag_glob(&mut args); + flag_heading(&mut args); + flag_hidden(&mut args); + flag_iglob(&mut args); + flag_ignore_case(&mut args); + flag_ignore_file(&mut args); + flag_invert_match(&mut args); + flag_line_number(&mut args); + flag_line_number_width(&mut args); + flag_line_regexp(&mut args); + flag_max_columns(&mut args); + flag_max_count(&mut args); + flag_max_filesize(&mut args); + flag_maxdepth(&mut args); + flag_mmap(&mut args); + flag_no_config(&mut args); + flag_no_ignore(&mut args); + flag_no_ignore_parent(&mut args); + flag_no_ignore_vcs(&mut args); + flag_no_messages(&mut args); + flag_null(&mut args); + flag_only_matching(&mut args); + flag_path_separator(&mut args); + flag_passthru(&mut args); + flag_pretty(&mut args); + flag_quiet(&mut args); + flag_regex_size_limit(&mut args); + flag_regexp(&mut args); + flag_replace(&mut args); + flag_search_zip(&mut args); + flag_smart_case(&mut args); + flag_sort_files(&mut args); + flag_text(&mut args); + flag_threads(&mut args); + flag_type(&mut args); + flag_type_add(&mut args); + flag_type_clear(&mut args); + flag_type_list(&mut args); + flag_type_not(&mut args); + flag_unrestricted(&mut args); + flag_vimgrep(&mut args); + flag_with_filename(&mut args); + flag_word_regexp(&mut args); + args +} + +fn arg_pattern(args: &mut Vec) { + const SHORT: &str = "A regular expression used for searching."; + const LONG: &str = long!("\ +A regular expression used for searching. To match a pattern beginning with a +dash, use the -e/--regexp flag. + +For example, to search for the literal '-foo', you can use this flag: + + rg -e -foo + +You can also use the special '--' delimiter to indicate that no more flags +will be provided. Namely, the following is equivalent to the above: + + rg -- -foo +"); + let arg = RGArg::positional("pattern", "PATTERN") + .help(SHORT).long_help(LONG) + .required_unless(&[ + "file", "files", "regexp", "type-list", + ]); + args.push(arg); +} + +fn arg_path(args: &mut Vec) { + const SHORT: &str = "A file or directory to search."; + const LONG: &str = long!("\ +A file or directory to search. Directories are searched recursively. Paths \ +specified on the command line override glob and ignore rules. \ +"); + let arg = RGArg::positional("path", "PATH") + .help(SHORT).long_help(LONG) + .multiple(); + args.push(arg); +} + +fn flag_after_context(args: &mut Vec) { + const SHORT: &str = "Show NUM lines after each match."; + const LONG: &str = long!("\ +Show NUM lines after each match. + +This overrides the --context flag. +"); + let arg = RGArg::flag("after-context", "NUM").short("A") + .help(SHORT).long_help(LONG) + .number() + .overrides("context"); + args.push(arg); +} + +fn flag_before_context(args: &mut Vec) { + const SHORT: &str = "Show NUM lines before each match."; + const LONG: &str = long!("\ +Show NUM lines before each match. + +This overrides the --context flag. +"); + let arg = RGArg::flag("before-context", "NUM").short("B") + .help(SHORT).long_help(LONG) + .number() + .overrides("context"); + args.push(arg); +} + +fn flag_case_sensitive(args: &mut Vec) { + const SHORT: &str = "Search case sensitively (default)."; + const LONG: &str = long!("\ +Search case sensitively. + +This overrides the -i/--ignore-case and -S/--smart-case flags. +"); + let arg = RGArg::switch("case-sensitive").short("s") + .help(SHORT).long_help(LONG) + .overrides("ignore-case") + .overrides("smart-case"); + args.push(arg); +} + +fn flag_color(args: &mut Vec) { + const SHORT: &str = "Controls when to use color."; + const LONG: &str = long!("\ +This flag controls when to use colors. The default setting is 'auto', which +means ripgrep will try to guess when to use colors. For example, if ripgrep is +printing to a terminal, then it will use colors, but if it is redirected to a +file or a pipe, then it will suppress color output. ripgrep will suppress color +output in some other circumstances as well. For example, if the TERM +environment variable is not set or set to 'dumb', then ripgrep will not use +colors. + +The possible values for this flag are: + + never Colors will never be used. + auto The default. ripgrep tries to be smart. + always Colors will always be used regardless of where output is sent. + ansi Like 'always', but emits ANSI escapes (even in a Windows console). + +When the --vimgrep flag is given to ripgrep, then the default value for the +--color flag changes to 'never'. +"); + let arg = RGArg::flag("color", "WHEN") + .help(SHORT).long_help(LONG) + .possible_values(&["never", "auto", "always", "ansi"]) + .default_value_if("never", "vimgrep"); + args.push(arg); +} + +fn flag_colors(args: &mut Vec) { + const SHORT: &str = "Configure color settings and styles."; + const LONG: &str = long!("\ +This flag specifies color settings for use in the output. This flag may be +provided multiple times. Settings are applied iteratively. Colors are limited +to one of eight choices: red, blue, green, cyan, magenta, yellow, white and +black. Styles are limited to nobold, bold, nointense or intense. + +The format of the flag is {type}:{attribute}:{value}. {type} should be one of +path, line, column or match. {attribute} can be fg, bg or style. {value} is +either a color (for fg and bg) or a text style. A special format, {type}:none, +will clear all color settings for {type}. + +For example, the following command will change the match color to magenta and +the background color for line numbers to yellow: + + rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo. + +Extended colors can be used for {value} when the terminal supports ANSI color +sequences. These are specified as either 'x' (256-color) or 'x,x,x' (24-bit +truecolor) where x is a number between 0 and 255 inclusive. + +For example, the following command will change the match background color to +that represented by the rgb value (0,128,255): + + rg --colors 'match:bg:0,128,255' + +Note that the the intense and nointense style flags will have no effect when +used alongside these extended color codes. +"); + let arg = RGArg::flag("colors", "COLOR_SPEC") + .help(SHORT).long_help(LONG) + .multiple(); + args.push(arg); +} + +fn flag_column(args: &mut Vec) { + const SHORT: &str = "Show column numbers."; + const LONG: &str = long!("\ +Show column numbers (1-based). This only shows the column numbers for the first +match on each line. This does not try to account for Unicode. One byte is equal +to one column. This implies --line-number. +"); + let arg = RGArg::switch("column") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_context(args: &mut Vec) { + const SHORT: &str = "Show NUM lines before and after each match."; + const LONG: &str = long!("\ +Show NUM lines before and after each match. This is equivalent to providing +both the -B/--before-context and -A/--after-context flags with the same value. + +This overrides both the -B/--before-context and -A/--after-context flags. +"); + let arg = RGArg::flag("context", "NUM").short("C") + .help(SHORT).long_help(LONG) + .number() + .overrides("before-context") + .overrides("after-context"); + args.push(arg); +} + +fn flag_context_separator(args: &mut Vec) { + const SHORT: &str = "Set the context separator string."; + const LONG: &str = long!("\ +The string used to separate non-contiguous context lines in the output. Escape +sequences like \\x7F or \\t may be used. The default value is --. +"); + let arg = RGArg::flag("context-separator", "SEPARATOR") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_count(args: &mut Vec) { + const SHORT: &str = "Only show the count of matches for each file."; + const LONG: &str = long!("\ +This flag suppresses normal output and shows the number of lines that match +the given patterns for each file searched. Each file containing a match has its +path and count printed on each line. Note that this reports the number of lines +that match and not the total number of matches. + +If only one file is given to ripgrep, then only the count is printed if there +is a match. The --with-filename flag can be used to force printing the file +path in this case. +"); + let arg = RGArg::switch("count").short("c") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_debug(args: &mut Vec) { + const SHORT: &str = "Show debug messages."; + const LONG: &str = long!("\ +Show debug messages. Please use this when filing a bug report. +"); + let arg = RGArg::switch("debug") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_dfa_size_limit(args: &mut Vec) { + const SHORT: &str = "The upper size limit of the regex DFA."; + const LONG: &str = long!("\ +The upper size limit of the regex DFA. The default limit is 10M. This should +only be changed on very large regex inputs where the (slower) fallback regex +engine may otherwise be used if the limit is reached. + +The argument accepts the same size suffixes as allowed in with the +--max-filesize flag. +"); + let arg = RGArg::flag("dfa-size-limit", "NUM+SUFFIX?") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_encoding(args: &mut Vec) { + const SHORT: &str = "Specify the text encoding of files to search."; + const LONG: &str = long!("\ +Specify the text encoding that ripgrep will use on all files searched. The +default value is 'auto', which will cause ripgrep to do a best effort automatic +detection of encoding on a per-file basis. Other supported values can be found +in the list of labels here: +https://encoding.spec.whatwg.org/#concept-encoding-get +"); + let arg = RGArg::flag("encoding", "ENCODING").short("E") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_file(args: &mut Vec) { + const SHORT: &str = "Search for patterns from the given file."; + const LONG: &str = long!("\ +Search for patterns from the given file, with one pattern per line. When this +flag is used multiple times or in combination with the -e/--regexp flag, +then all patterns provided are searched. Empty pattern lines will match all +input lines, and the newline is not counted as part of the pattern. + +A line is printed if and only if it matches at least one of the patterns. +"); + let arg = RGArg::flag("file", "PATH").short("f") + .help(SHORT).long_help(LONG) + .multiple() + .allow_leading_hyphen(); + args.push(arg); +} + +fn flag_files(args: &mut Vec) { + const SHORT: &str = "Print each file that would be searched."; + const LONG: &str = long!("\ +Print each file that would be searched without actually performing the search. +This is useful to determine whether a particular file is being search or not. +"); + let arg = RGArg::switch("files") + .help(SHORT).long_help(LONG) + // This also technically conflicts with pattern, but the first file + // path will actually be in pattern. + .conflicts(&["file", "regexp", "type-list"]); + args.push(arg); +} + +fn flag_files_with_matches(args: &mut Vec) { + const SHORT: &str = "Only print the paths with at least one match."; + const LONG: &str = long!("\ +Only print the paths with at least one match. + +This overrides --file-without-match. +"); + let arg = RGArg::switch("files-with-matches").short("l") + .help(SHORT).long_help(LONG) + .overrides("files-without-match"); + args.push(arg); +} + +fn flag_files_without_match(args: &mut Vec) { + const SHORT: &str = "Only print the paths that contain zero matches."; + const LONG: &str = long!("\ +Only print the paths that contain zero matches. + +This overrides --file-with-matches. +"); + let arg = RGArg::switch("files-without-match") + .help(SHORT).long_help(LONG) + .overrides("files-with-matches"); + args.push(arg); +} + +fn flag_fixed_strings(args: &mut Vec) { + const SHORT: &str = "Treat the pattern as a literal string."; + const LONG: &str = long!("\ +Treat the pattern as a literal string instead of a regular expression. When +this flag is used, special regular expression meta characters such as .(){}*+ +do not need to be escaped. +"); + let arg = RGArg::switch("fixed-strings").short("F") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_follow(args: &mut Vec) { + const SHORT: &str = "Follow symbolic links."; + const LONG: &str = long!("\ +When this flag is enabled, ripgrep will follow symbolic links while traversing +directories. This is disabled by default. Note that ripgrep will check for +symbolic link loops and report errors if it finds one. +"); + let arg = RGArg::switch("follow").short("L") + .help(SHORT).long_help(LONG); + args.push(arg); } -fn validate_number(s: String) -> Result<(), String> { - s.parse::().map(|_|()).map_err(|err| err.to_string()) +fn flag_glob(args: &mut Vec) { + const SHORT: &str = "Include or exclude files and directories."; + const LONG: &str = long!("\ +Include or exclude files and directories for searching that match the given +glob. This always overrides any other ignore logic. Multiple glob flags may be +used. Globbing rules match .gitignore globs. Precede a glob with a ! to exclude +it. +"); + let arg = RGArg::flag("glob", "GLOB").short("g") + .help(SHORT).long_help(LONG) + .multiple() + .allow_leading_hyphen(); + args.push(arg); +} + +fn flag_heading(args: &mut Vec) { + const SHORT: &str = "Print matches grouped by each file."; + const LONG: &str = long!("\ +This flag prints the file path above clusters of matches from each file instead +of printing the file path as a prefix for each matched line. This is the +default mode when printing to a terminal. + +This overrides the --no-heading flag. +"); + let arg = RGArg::switch("heading") + .help(SHORT).long_help(LONG) + .overrides("no-heading"); + args.push(arg); + + const NO_SHORT: &str = "Don't group matches by each file."; + const NO_LONG: &str = long!("\ +Don't group matches by each file. If --no-heading is provided in addition to +the -H/--with-filename flag, then file paths will be printed as a prefix for +every matched line. This is the default mode when not printing to a terminal. + +This overrides the --heading flag. +"); + let arg = RGArg::switch("no-heading") + .help(NO_SHORT).long_help(NO_LONG) + .overrides("heading"); + args.push(arg); +} + +fn flag_hidden(args: &mut Vec) { + const SHORT: &str = "Search hidden files and directories."; + const LONG: &str = long!("\ +Search hidden files and directories. By default, hidden files and directories +are skipped. Note that if a hidden file or a directory is whitelisted in an +ignore file, then it will be searched even if this flag isn't provided. +"); + let arg = RGArg::switch("hidden") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_iglob(args: &mut Vec) { + const SHORT: &str = + "Include or exclude files and directories case insensitively."; + const LONG: &str = long!("\ +Include or exclude files and directories for searching that match the given +glob. This always overrides any other ignore logic. Multiple glob flags may be +used. Globbing rules match .gitignore globs. Precede a glob with a ! to exclude +it. Globs are matched case insensitively. +"); + let arg = RGArg::flag("iglob", "GLOB") + .help(SHORT).long_help(LONG) + .multiple() + .allow_leading_hyphen(); + args.push(arg); +} + +fn flag_ignore_case(args: &mut Vec) { + const SHORT: &str = "Case insensitive search."; + const LONG: &str = long!("\ +When this flag is provided, the given patterns will be searched case +insensitively. The case insensitivity rules used by ripgrep conform to +Unicode's \"simple\" case folding rules. + +This flag overrides -s/--case-sensitive and -S/--smart-case. +"); + let arg = RGArg::switch("ignore-case").short("i") + .help(SHORT).long_help(LONG) + .overrides("case-sensitive") + .overrides("smart-case"); + args.push(arg); +} + +fn flag_ignore_file(args: &mut Vec) { + const SHORT: &str = "Specify additional ignore files."; + const LONG: &str = long!("\ +Specify one or more files which contain ignore patterns. These patterns are +applied after the patterns found in .gitignore and .ignore are applied. Ignore +patterns should be in the gitignore format and are matched relative to the +current working directory. Multiple additional ignore files can be specified +by using the --ignore-file flag several times. When specifying multiple ignore +files, earlier files have lower precedence than later files. + +If you are looking for a way to include or exclude files and directories +directly on the command line, then used -g instead. +"); + let arg = RGArg::flag("ignore-file", "PATH") + .help(SHORT).long_help(LONG) + .multiple() + .allow_leading_hyphen(); + args.push(arg); +} + +fn flag_invert_match(args: &mut Vec) { + const SHORT: &str = "Invert matching."; + const LONG: &str = long!("\ +Invert matching. Show lines that do not match the given patterns. +"); + let arg = RGArg::switch("invert-match").short("v") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_line_number(args: &mut Vec) { + const SHORT: &str = "Show line numbers."; + const LONG: &str = long!("\ +Show line numbers (1-based). This is enabled by default when searching in a +terminal. +"); + let arg = RGArg::switch("line-number").short("n") + .help(SHORT).long_help(LONG) + .overrides("no-line-number"); + args.push(arg); + + const NO_SHORT: &str = "Suppress line numbers."; + const NO_LONG: &str = long!("\ +Suppress line numbers. This is enabled by default when not searching in a +terminal. +"); + let arg = RGArg::switch("no-line-number").short("N") + .help(NO_SHORT).long_help(NO_LONG) + .overrides("line-number"); + args.push(arg); +} + +fn flag_line_number_width(args: &mut Vec) { + const SHORT: &str = "Left pad line numbers up to NUM width."; + const LONG: &str = long!("\ +Left pad line numbers up to NUM width. Space is used as the default padding +character. This has no effect if --no-line-number is enabled. +"); + let arg = RGArg::flag("line-number-width", "NUM") + .help(SHORT).long_help(LONG) + .line_number_width(); + args.push(arg); +} + +fn flag_line_regexp(args: &mut Vec) { + const SHORT: &str = "Only show matches surrounded by line boundaries."; + const LONG: &str = long!("\ +Only show matches surrounded by line boundaries. This is equivalent to putting +^...$ around all of the search patterns. In other words, this only prints lines +where the entire line participates in a match. + +This overrides the --word-regexp flag. +"); + let arg = RGArg::switch("line-regexp").short("x") + .help(SHORT).long_help(LONG) + .overrides("word-regexp"); + args.push(arg); +} + +fn flag_max_columns(args: &mut Vec) { + const SHORT: &str = "Don't print lines longer than this limit."; + const LONG: &str = long!("\ +Don't print lines longer than this limit in bytes. Longer lines are omitted, +and only the number of matches in that line is printed. +"); + let arg = RGArg::flag("max-columns", "NUM").short("M") + .help(SHORT).long_help(LONG) + .number(); + args.push(arg); +} + +fn flag_max_count(args: &mut Vec) { + const SHORT: &str = "Limit the number of matches."; + const LONG: &str = long!("\ +Limit the number of matching lines per file searched to NUM. +"); + let arg = RGArg::flag("max-count", "NUM").short("m") + .help(SHORT).long_help(LONG) + .number(); + args.push(arg); +} + +fn flag_max_filesize(args: &mut Vec) { + const SHORT: &str = "Ignore files larger than NUM in size."; + const LONG: &str = long!("\ +Ignore files larger than NUM in size. This does not apply to directories. + +The input format accepts suffixes of K, M or G which correspond to kilobytes, +megabytes and gigabytes, respectively. If no suffix is provided the input is +treated as bytes. + +Examples: --max-filesize 50K or --max-filesize 80M +"); + let arg = RGArg::flag("max-filesize", "NUM+SUFFIX?") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_maxdepth(args: &mut Vec) { + const SHORT: &str = "Descend at most NUM directories."; + const LONG: &str = long!("\ +Limit the depth of directory traversal to NUM levels beyond the paths given. A +value of zero only searches the explicitly given paths themselves. + +For example, 'rg --maxdepth 0 dir/' is a no-op because dir/ will not be +descended into. 'rg --maxdepth 1 dir/' will search only the direct children of +'dir'. +"); + let arg = RGArg::flag("maxdepth", "NUM") + .help(SHORT).long_help(LONG) + .number(); + args.push(arg); +} + +fn flag_mmap(args: &mut Vec) { + const SHORT: &str = "Search using memory maps when possible."; + const LONG: &str = long!("\ +Search using memory maps when possible. This is enabled by default when ripgrep +thinks it will be faster. + +Memory map searching doesn't currently support all options, so if an +incompatible option (e.g., --context) is given with --mmap, then memory maps +will not be used. + +Note that ripgrep may abort unexpectedly when --mmap if it searches a file that +is simultaneously truncated. + +This flag overrides --no-mmap. +"); + let arg = RGArg::switch("mmap") + .help(SHORT).long_help(LONG) + .overrides("no-mmap"); + args.push(arg); + + const NO_SHORT: &str = "Never use memory maps."; + const NO_LONG: &str = long!("\ +Never use memory maps, even when they might be faster. + +This flag overrides --mmap. +"); + let arg = RGArg::switch("no-mmap") + .help(NO_SHORT).long_help(NO_LONG) + .overrides("mmap"); + args.push(arg); +} + +fn flag_no_config(args: &mut Vec) { + const SHORT: &str = "Never read configuration files."; + const LONG: &str = long!("\ +Never read configuration files. When this flag is present, ripgrep will not +respect the RIPGREP_CONFIG_PATH environment variable. + +If ripgrep ever grows a feature to automatically read configuration files in +pre-defined locations, then this flag will also disable that behavior as well. +"); + let arg = RGArg::switch("no-config") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_no_ignore(args: &mut Vec) { + const SHORT: &str = "Don't respect ignore files."; + const LONG: &str = long!("\ +Don't respect ignore files (.gitignore, .ignore, etc.). This implies +--no-ignore-parent and --no-ignore-vcs. +"); + let arg = RGArg::switch("no-ignore") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_no_ignore_parent(args: &mut Vec) { + const SHORT: &str = "Don't respect ignore files in parent directories."; + const LONG: &str = long!("\ +Don't respect ignore files (.gitignore, .ignore, etc.) in parent directories. +"); + let arg = RGArg::switch("no-ignore-parent") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_no_ignore_vcs(args: &mut Vec) { + const SHORT: &str = "Don't respect VCS ignore files."; + const LONG: &str = long!("\ +Don't respect version control ignore files (.gitignore, etc.). This implies +--no-ignore-parent for VCS files. Note that .ignore files will continue to be +respected. +"); + let arg = RGArg::switch("no-ignore-vcs") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_no_messages(args: &mut Vec) { + const SHORT: &str = "Suppress all error messages."; + const LONG: &str = long!("\ +Suppress all error messages. This provides the same behavior as redirecting +stderr to /dev/null on Unix-like systems. +"); + let arg = RGArg::switch("no-messages") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_null(args: &mut Vec) { + const SHORT: &str = "Print a NUL byte after file paths."; + const LONG: &str = long!("\ +Whenever a file path is printed, follow it with a NUL byte. This includes +printing file paths before matches, and when printing a list of matching files +such as with --count, --files-with-matches and --files. This option is useful +for use with xargs. +"); + let arg = RGArg::switch("null").short("0") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_only_matching(args: &mut Vec) { + const SHORT: &str = "Print only matches parts of a line."; + const LONG: &str = long!("\ +Print only the matched (non-empty) parts of a matching line, with each such +part on a separate output line. +"); + let arg = RGArg::switch("only-matching").short("o") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_path_separator(args: &mut Vec) { + const SHORT: &str = "Set the path separator."; + const LONG: &str = long!("\ +Set the path separator to use when printing file paths. This defaults to your +platform's path separator, which is / on Unix and \\ on Windows. This flag is +intended for overriding the default when the environment demands it (e.g., +cygwin). A path separator is limited to a single byte. +"); + let arg = RGArg::flag("path-separator", "SEPARATOR") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_passthru(args: &mut Vec) { + const SHORT: &str = "Print both matching and non-matching lines."; + const LONG: &str = long!("\ +Print both matching and non-matching lines. + +Another way to achieve a similar effect is by modifying your pattern to match +the empty string. For example, if you are searching using 'rg foo' then using +'rg \"^|foo\"' instead will emit every line in every file searched, but only +occurrences of 'foo' will be highlighted. This flag enables the same behavior +without needing to modify the pattern. + +This flag conflicts with the --only-matching and --replace flags. +"); + let arg = RGArg::switch("passthru") + .help(SHORT).long_help(LONG) + .alias("passthrough") + .conflicts(&["only-matching", "replace"]); + args.push(arg); +} + +fn flag_pretty(args: &mut Vec) { + const SHORT: &str = "Alias for --color always --heading --line-number."; + const LONG: &str = long!("\ +This is a convenience alias for '--color always --heading --line-number'. This +flag is useful when you still want pretty output even if you're piping ripgrep +to another program or file. For example: 'rg -p foo | less -R'. +"); + let arg = RGArg::switch("pretty").short("p") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_quiet(args: &mut Vec) { + const SHORT: &str = "Do not print anything to stdout."; + const LONG: &str = long!("\ +Do not print anything to stdout. If a match is found in a file, then ripgrep +will stop searching. This is useful when ripgrep is used only for its exit +code (which will be an error if no matches are found). +"); + let arg = RGArg::switch("quiet").short("q") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_regex_size_limit(args: &mut Vec) { + const SHORT: &str = "The upper size limit of the compiled regex."; + const LONG: &str = long!("\ +The upper size limit of the compiled regex. The default limit is 10M. + +The argument accepts the same size suffixes as allowed in the --max-filesize +flag. +"); + let arg = RGArg::flag("regex-size-limit", "NUM+SUFFIX?") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_regexp(args: &mut Vec) { + const SHORT: &str = "A pattern to search for."; + const LONG: &str = long!("\ +A pattern to search for. This option can be provided multiple times, where +all patterns given are searched. Lines matching at least one of the provided +patterns are printed. This flag can also be used when searching for patterns +that start with a dash. + +For example, to search for the literal '-foo', you can use this flag: + + rg -e -foo + +You can also use the special '--' delimiter to indicate that no more flags +will be provided. Namely, the following is equivalent to the above: + + rg -- -foo +"); + let arg = RGArg::flag("regexp", "PATTERN").short("e") + .help(SHORT).long_help(LONG) + .multiple() + .allow_leading_hyphen(); + args.push(arg); +} + +fn flag_replace(args: &mut Vec) { + const SHORT: &str = "Replace matches with the given text."; + const LONG: &str = long!("\ +Replace every match with the text given when printing results. Neither this +flag nor any other ripgrep flag will modify your files. + +Capture group indices (e.g., $5) and names (e.g., $foo) are supported in the +replacement string. + +Note that the replacement by default replaces each match, and NOT the entire +line. To replace the entire line, you should match the entire line. + +This flag can be used with the -o/--only-matching flag. +"); + let arg = RGArg::flag("replace", "REPLACEMENT_TEXT").short("r") + .help(SHORT).long_help(LONG) + .allow_leading_hyphen(); + args.push(arg); +} + +fn flag_search_zip(args: &mut Vec) { + const SHORT: &str = "Search in compressed files."; + const LONG: &str = long!("\ +Search in compressed files. Currently gz, bz2, xz, and lzma files are +supported. This option expects the decompression binaries to be available in +your PATH. +"); + let arg = RGArg::switch("search-zip").short("z") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_smart_case(args: &mut Vec) { + const SHORT: &str = "Smart case search."; + const LONG: &str = long!("\ +Searches case insensitively if the pattern is all lowercase. Search case +sensitively otherwise. + +This overrides the -s/--case-sensitive and -i/--ignore-case flags. +"); + let arg = RGArg::switch("smart-case").short("S") + .help(SHORT).long_help(LONG) + .overrides("case-sensitive") + .overrides("ignore-case"); + args.push(arg); +} + +fn flag_sort_files(args: &mut Vec) { + const SHORT: &str = "Sort results by file path. Implies --threads=1."; + const LONG: &str = long!("\ +Sort results by file path. Note that this currently disables all parallelism +and runs search in a single thread. +"); + let arg = RGArg::switch("sort-files") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_text(args: &mut Vec) { + const SHORT: &str = "Search binary files as if they were text."; + const LONG: &str = long!("\ +Search binary files as if they were text. When this flag is present, ripgrep's +binary file detection is disabled. This means that when a binary file is +searched, its contents may be printed if there is a match. This may cause +escape codes to be printed that alter the behavior of your terminal. + +When binary file detection is enabled it is imperfect. In general, it uses +a simple heuristic. If a NUL byte is seen during search, then the file is +considered binary and search stops (unless this flag is present). + +Note that when the `-u/--unrestricted` flag is provided for a third time, then +this flag is automatically enabled. +"); + let arg = RGArg::switch("text").short("a") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_threads(args: &mut Vec) { + const SHORT: &str = "The approximate number of threads to use."; + const LONG: &str = long!("\ +The approximate number of threads to use. A value of 0 (which is the default) +causes ripgrep to choose the thread count using heuristics. +"); + let arg = RGArg::flag("threads", "NUM").short("j") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_type(args: &mut Vec) { + const SHORT: &str = "Only search files matching TYPE."; + const LONG: &str = long!("\ +Only search files matching TYPE. Multiple type flags may be provided. Use the +--type-list flag to list all available types. +"); + let arg = RGArg::flag("type", "TYPE").short("t") + .help(SHORT).long_help(LONG) + .multiple(); + args.push(arg); +} + +fn flag_type_add(args: &mut Vec) { + const SHORT: &str = "Add a new glob for a file type."; + const LONG: &str = long!("\ +Add a new glob for a particular file type. Only one glob can be added at a +time. Multiple --type-add flags can be provided. Unless --type-clear is used, +globs are added to any existing globs defined inside of ripgrep. + +Note that this MUST be passed to every invocation of ripgrep. Type settings are +NOT persisted. + +Example: + + rg --type-add 'foo:*.foo' -tfoo PATTERN. + +--type-add can also be used to include rules from other types with the special +include directive. The include directive permits specifying one or more other +type names (separated by a comma) that have been defined and its rules will +automatically be imported into the type specified. For example, to create a +type called src that matches C++, Python and Markdown files, one can use: + + --type-add 'src:include:cpp,py,md' + +Additional glob rules can still be added to the src type by using the +--type-add flag again: + + --type-add 'src:include:cpp,py,md' --type-add 'src:*.foo' + +Note that type names must consist only of Unicode letters or numbers. +Punctuation characters are not allowed. +"); + let arg = RGArg::flag("type-add", "TYPE_SPEC") + .help(SHORT).long_help(LONG) + .multiple(); + args.push(arg); +} + +fn flag_type_clear(args: &mut Vec) { + const SHORT: &str = "Clear globs for a file type."; + const LONG: &str = long!("\ +Clear the file type globs previously defined for TYPE. This only clears the +default type definitions that are found inside of ripgrep. + +Note that this MUST be passed to every invocation of ripgrep. Type settings are +NOT persisted. +"); + let arg = RGArg::flag("type-clear", "TYPE") + .help(SHORT).long_help(LONG) + .multiple(); + args.push(arg); +} + +fn flag_type_not(args: &mut Vec) { + const SHORT: &str = "Do not search files matching TYPE."; + const LONG: &str = long!("\ +Do not search files matching TYPE. Multiple type-not flags may be provided. Use +the --type-list flag to list all available types. +"); + let arg = RGArg::flag("type-not", "TYPE").short("T") + .help(SHORT).long_help(LONG) + .multiple(); + args.push(arg); +} + +fn flag_type_list(args: &mut Vec) { + const SHORT: &str = "Show all supported file types."; + const LONG: &str = long!("\ +Show all supported file types and their corresponding globs. +"); + let arg = RGArg::switch("type-list") + .help(SHORT).long_help(LONG) + // This also technically conflicts with PATTERN, but the first file + // path will actually be in PATTERN. + .conflicts(&["file", "files", "PATTERN", "regexp"]); + args.push(arg); +} + +fn flag_unrestricted(args: &mut Vec) { + const SHORT: &str = "Reduce the level of \"smart\" searching."; + const LONG: &str = long!("\ +Reduce the level of \"smart\" searching. A single -u won't respect .gitignore +(etc.) files. Two -u flags will additionally search hidden files and +directories. Three -u flags will additionally search binary files. + +-uu is roughly equivalent to grep -r and -uuu is roughly equivalent to grep -a +-r. +"); + let arg = RGArg::switch("unrestricted").short("u") + .help(SHORT).long_help(LONG) + .multiple(); + args.push(arg); +} + +fn flag_vimgrep(args: &mut Vec) { + const SHORT: &str = "Show results in vim compatible format."; + const LONG: &str = long!("\ +Show results with every match on its own line, including line numbers and +column numbers. With this option, a line with more than one match will be +printed more than once. +"); + let arg = RGArg::switch("vimgrep") + .help(SHORT).long_help(LONG); + args.push(arg); +} + +fn flag_with_filename(args: &mut Vec) { + const SHORT: &str = "Print the file path with the matched lines."; + const LONG: &str = long!("\ +Display the file path for matches. This is the default when more than one +file is searched. If --heading is enabled (the default when printing to a +terminal), the file path will be shown above clusters of matches from each +file; otherwise, the file name will be shown as a prefix for each matched line. + +This flag overrides --no-filename. +"); + let arg = RGArg::switch("with-filename").short("H") + .help(SHORT).long_help(LONG) + .overrides("no-filename"); + args.push(arg); + + const NO_SHORT: &str = "Never print the file path with the matched lines."; + const NO_LONG: &str = long!("\ +Never print the file path with the matched lines. This is the default when +ripgrep is explicitly instructed to search one file or stdin. + +This flag overrides --with-filename. +"); + let arg = RGArg::switch("no-filename") + .help(NO_SHORT).long_help(NO_LONG) + .overrides("with-filename"); + args.push(arg); +} + +fn flag_word_regexp(args: &mut Vec) { + const SHORT: &str = "Only show matches surrounded by word boundaries."; + const LONG: &str = long!("\ +Only show matches surrounded by word boundaries. This is roughly equivalent to +putting \\b before and after all of the search patterns. + +This overrides the --line-regexp flag. +"); + let arg = RGArg::switch("word-regexp").short("w") + .help(SHORT).long_help(LONG) + .overrides("line-regexp"); + args.push(arg); } diff --git a/src/args.rs b/src/args.rs index a0c1d7db1..d0990fdc0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,14 +3,12 @@ use std::env; use std::ffi::OsStr; use std::fs; use std::io::{self, BufRead}; -use std::ops; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use clap; use encoding_rs::Encoding; -use env_logger; use grep::{Grep, GrepBuilder, Error as GrepError}; use log; use num_cpus; @@ -27,6 +25,8 @@ use printer::{ColorSpecs, Printer}; use unescape::unescape; use worker::{Worker, WorkerBuilder}; +use config; +use logger::Logger; use Result; /// `Args` are transformed/normalized from `ArgMatches`. @@ -89,18 +89,59 @@ impl Args { /// /// Also, initialize a global logger. pub fn parse() -> Result { - let matches = app::app().get_matches(); + // We parse the args given on CLI. This does not include args from + // the config. We use the CLI args as an initial configuration while + // trying to parse config files. If a config file exists and has + // arguments, then we re-parse argv, otherwise we just use the matches + // we have here. + let early_matches = ArgMatches(app::app().get_matches()); + + if let Err(err) = Logger::init() { + errored!("failed to initialize logger: {}", err); + } + if early_matches.is_present("debug") { + log::set_max_level(log::LevelFilter::Debug); + } else { + log::set_max_level(log::LevelFilter::Warn); + } - let mut logb = env_logger::LogBuilder::new(); + let matches = Args::matches(early_matches); + // The logging level may have changed if we brought in additional + // arguments from a configuration file, so recheck it and set the log + // level as appropriate. if matches.is_present("debug") { - logb.filter(None, log::LogLevelFilter::Debug); + log::set_max_level(log::LevelFilter::Debug); } else { - logb.filter(None, log::LogLevelFilter::Warn); + log::set_max_level(log::LevelFilter::Warn); } - if let Err(err) = logb.init() { - errored!("failed to initialize logger: {}", err); + matches.to_args() + } + + /// Run clap and return the matches. If clap determines a problem with the + /// user provided arguments (or if --help or --version are given), then an + /// error/usage/version will be printed and the process will exit. + /// + /// If there are no additional arguments from the environment (e.g., a + /// config file), then the given matches are returned as is. + fn matches(early_matches: ArgMatches<'static>) -> ArgMatches<'static> { + // If the end user says no config, then respect it. + if early_matches.is_present("no-config") { + debug!("not reading config files because --no-config is present"); + return early_matches; + } + // If the user wants ripgrep to use a config file, then parse args + // from that first. + let mut args = config::args(early_matches.is_present("no-messages")); + if args.is_empty() { + return early_matches; + } + let mut cliargs = env::args_os(); + if let Some(bin) = cliargs.next() { + args.insert(0, bin); } - ArgMatches(matches).to_args() + args.extend(cliargs); + debug!("final argv: {:?}", args); + ArgMatches(app::app().get_matches_from(args)) } /// Returns true if ripgrep should print the files it will search and exit @@ -306,11 +347,6 @@ impl Args { /// several options/flags. struct ArgMatches<'a>(clap::ArgMatches<'a>); -impl<'a> ops::Deref for ArgMatches<'a> { - type Target = clap::ArgMatches<'a>; - fn deref(&self) -> &clap::ArgMatches<'a> { &self.0 } -} - impl<'a> ArgMatches<'a> { /// Convert the result of parsing CLI arguments into ripgrep's /// configuration. @@ -377,7 +413,7 @@ impl<'a> ArgMatches<'a> { /// Return all file paths that ripgrep should search. fn paths(&self) -> Vec { - let mut paths: Vec = match self.values_of_os("PATH") { + let mut paths: Vec = match self.values_of_os("path") { None => vec![], Some(vals) => vals.map(|p| Path::new(p).to_path_buf()).collect(), }; @@ -386,7 +422,7 @@ impl<'a> ArgMatches<'a> { if self.is_present("file") || self.is_present("files") || self.is_present("regexp") { - if let Some(path) = self.value_of_os("PATTERN") { + if let Some(path) = self.value_of_os("pattern") { paths.insert(0, Path::new(path).to_path_buf()); } } @@ -452,7 +488,7 @@ impl<'a> ArgMatches<'a> { match self.values_of_os("regexp") { None => { if self.values_of_os("file").is_none() { - if let Some(os_pat) = self.value_of_os("PATTERN") { + if let Some(os_pat) = self.value_of_os("pattern") { pats.push(self.os_str_pattern(os_pat)?); } } @@ -646,7 +682,7 @@ impl<'a> ArgMatches<'a> { /// Returns the replacement string as UTF-8 bytes if it exists. fn replace(&self) -> Option> { - self.value_of_lossy("replace").map(|s| s.into_owned().into_bytes()) + self.value_of_lossy("replace").map(|s| s.into_bytes()) } /// Returns the unescaped context separator in UTF-8 bytes. @@ -696,9 +732,9 @@ impl<'a> ArgMatches<'a> { /// Returns the user's color choice based on command line parameters and /// environment. fn color_choice(&self) -> termcolor::ColorChoice { - let preference = match self.0.value_of_lossy("color") { + let preference = match self.value_of_lossy("color") { None => "auto".to_string(), - Some(v) => v.into_owned(), + Some(v) => v, }; if preference == "always" { termcolor::ColorChoice::Always @@ -744,7 +780,7 @@ impl<'a> ArgMatches<'a> { /// A `None` encoding implies that the encoding should be automatically /// detected on a per-file basis. fn encoding(&self) -> Result> { - match self.0.value_of_lossy("encoding") { + match self.value_of_lossy("encoding") { None => Ok(None), Some(label) => { if label == "auto" { @@ -943,6 +979,35 @@ impl<'a> ArgMatches<'a> { Some(v) => v.parse().map(Some).map_err(From::from), } } + + // The following methods mostly dispatch to the underlying clap methods + // directly. Methods that would otherwise get a single value will fetch + // all values and return the last one. (Clap returns the first one.) We + // only define the ones we need. + + fn is_present(&self, name: &str) -> bool { + self.0.is_present(name) + } + + fn occurrences_of(&self, name: &str) -> u64 { + self.0.occurrences_of(name) + } + + fn value_of_lossy(&self, name: &str) -> Option { + self.values_of_lossy(name).and_then(|mut vals| vals.pop()) + } + + fn values_of_lossy(&self, name: &str) -> Option> { + self.0.values_of_lossy(name) + } + + fn value_of_os(&'a self, name: &str) -> Option<&'a OsStr> { + self.values_of_os(name).and_then(|it| it.last()) + } + + fn values_of_os(&'a self, name: &str) -> Option> { + self.0.values_of_os(name) + } } fn pattern_to_str(s: &OsStr) -> Result<&str> { diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..c47e6a504 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,195 @@ +// 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::error::Error; +use std::fs::File; +use std::io::{self, BufRead}; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; + +use Result; + +/// Return a sequence of arguments derived from ripgrep rc configuration files. +/// +/// If no_messages is false and there was a problem reading a config file, +/// then errors are printed to stderr. +pub fn args(no_messages: bool) -> 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) => { + if !no_messages { + eprintln!("{}", err); + } + return vec![]; + } + }; + if !no_messages && !errs.is_empty() { + for err in errs { + eprintln!("{}:{}", config_path.display(), err); + } + } + 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(); + match File::open(&path) { + Ok(file) => parse_reader(file), + Err(err) => errored!("{}: {}", path.display(), err), + } +} + +/// 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, +) -> Result<(Vec, Vec>)> { + let mut bufrdr = io::BufReader::new(rdr); + let (mut args, mut errs) = (vec![], vec![]); + let mut line = vec![]; + let mut line_number = 0; + while { + line.clear(); + line_number += 1; + bufrdr.read_until(b'\n', &mut line)? > 0 + } { + trim(&mut line); + if line.is_empty() || line[0] == b'#' { + continue; + } + match bytes_to_os_string(&line) { + Ok(osstr) => { + args.push(osstr); + } + Err(err) => { + errs.push(format!("{}: {}", line_number, err).into()); + } + } + } + Ok((args, errs)) +} + +/// Trim the given bytes of whitespace according to the ASCII definition. +fn trim(x: &mut Vec) { + let upto = x.iter().take_while(|b| is_space(**b)).count(); + x.drain(..upto); + let revto = x.len() - x.iter().rev().take_while(|b| is_space(**b)).count(); + x.drain(revto..); +} + +/// Returns true if and only if the given byte is an ASCII space character. +fn is_space(b: u8) -> bool { + b == b'\t' + || b == b'\n' + || b == b'\x0B' + || b == b'\x0C' + || b == b'\r' + || b == b' ' +} + +/// On Unix, get an OsString from raw bytes. +#[cfg(unix)] +fn bytes_to_os_string(bytes: &[u8]) -> Result { + use std::os::unix::ffi::OsStringExt; + Ok(OsString::from_vec(bytes.to_vec())) +} + +/// On non-Unix (like Windows), require UTF-8. +#[cfg(not(unix))] +fn bytes_to_os_string(bytes: &[u8]) -> Result { + String::from_utf8(bytes.to_vec()).map(OsString::from).map_err(From::from) +} + +#[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"), + ]); + } +} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 000000000..8bd7e09cf --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,57 @@ +// This module defines a super simple logger that works with the `log` crate. +// We don't need anything fancy; just basic log levels and the ability to +// print to stderr. We therefore avoid bringing in extra dependencies just +// for this functionality. + +use log::{self, Log}; + +/// The simplest possible logger that logs to stderr. +/// +/// This logger does no filtering. Instead, it relies on the `log` crates +/// filtering via its global max_level setting. +#[derive(Debug)] +pub struct Logger(()); + +const LOGGER: &'static Logger = &Logger(()); + +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) + } +} + +impl Log for Logger { + fn enabled(&self, _: &log::Metadata) -> bool { + // We set the log level via log::set_max_level, so we don't need to + // implement filtering here. + true + } + + fn log(&self, record: &log::Record) { + match (record.file(), record.line()) { + (Some(file), Some(line)) => { + eprintln!( + "{}/{}/{}:{}: {}", + record.level(), record.target(), + file, line, record.args()); + } + (Some(file), None) => { + eprintln!( + "{}/{}/{}: {}", + record.level(), record.target(), file, record.args()); + } + _ => { + eprintln!( + "{}/{}: {}", + record.level(), record.target(), record.args()); + } + } + } + + fn flush(&self) { + // We use eprintln! which is flushed on every call. + } +} diff --git a/src/main.rs b/src/main.rs index fea89e441..b3b192c1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ extern crate bytecount; #[macro_use] extern crate clap; extern crate encoding_rs; -extern crate env_logger; extern crate globset; extern crate grep; extern crate ignore; @@ -40,8 +39,10 @@ macro_rules! errored { mod app; mod args; +mod config; mod decoder; mod decompressor; +mod logger; mod pathutil; mod printer; mod search_buffer; @@ -49,7 +50,7 @@ mod search_stream; mod unescape; mod worker; -pub type Result = result::Result>; +pub type Result = result::Result>; fn main() { reset_sigpipe(); diff --git a/tests/tests.rs b/tests/tests.rs index dc19350c0..ecc840e79 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1711,6 +1711,22 @@ fn compressed_failing_gzip() { assert_eq!(err.contains("not in gzip format"), true); } +sherlock!(feature_196_persistent_config, "sherlock", +|wd: WorkDir, mut cmd: Command| { + // Make sure we get no matches by default. + wd.assert_err(&mut cmd); + + // Now add our config file, and make sure it impacts ripgrep. + wd.create(".ripgreprc", "--ignore-case"); + cmd.env("RIPGREP_CONFIG_PATH", ".ripgreprc"); + let lines: String = wd.stdout(&mut cmd); + let expected = "\ +For the Doctor Watsons of this world, as opposed to the Sherlock +be, to a very large extent, the result of luck. Sherlock Holmes +"; + assert_eq!(lines, expected); +}); + #[test] fn feature_740_passthru() { let wd = WorkDir::new("feature_740"); diff --git a/tests/workdir.rs b/tests/workdir.rs index ea5408a40..3c47e9483 100644 --- a/tests/workdir.rs +++ b/tests/workdir.rs @@ -93,6 +93,7 @@ impl WorkDir { /// this working directory. pub fn command(&self) -> process::Command { let mut cmd = process::Command::new(&self.bin()); + cmd.env_remove("RIPGREP_CONFIG_PATH"); cmd.current_dir(&self.dir); cmd }