BurntSushi/ripgrep
CLI flags
ripgrep's CLI is unusually large (over 100 flags) and unusually well-documented (rg --help long-form is ~5000 lines). Both come out of a single registry of Flag implementations.
The pipeline
graph LR
Argv["std::env::args_os()"] --> Config["read RIPGREP_CONFIG_PATH"]
Config --> Lex["lexopt::Parser"]
Lex --> Dispatch["match flag name → Flag::update"]
Dispatch --> Low["LowArgs"]
Low --> Hi["HiArgs::from_low_args"]
Hi --> Run["main.rs::run"]Every box maps to one file:
- argv read in
crates/core/main.rs::main - config-file parse in
crates/core/flags/config.rs - lexing and dispatch in
crates/core/flags/parse.rs - flag table in
crates/core/flags/defs.rs - low-level args struct in
crates/core/flags/lowargs.rs - high-level args struct in
crates/core/flags/hiargs.rs
The Flag trait
Defined in crates/core/flags/mod.rs. Every flag is a unit struct that implements:
trait Flag: Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static {
fn is_switch(&self) -> bool;
fn name_short(&self) -> Option<u8> { None }
fn name_long(&self) -> &'static str;
fn aliases(&self) -> &'static [&'static str] { &[] }
fn name_negated(&self) -> Option<&'static str> { None }
fn doc_variable(&self) -> Option<&'static str> { None }
fn doc_category(&self) -> Category;
fn doc_short(&self) -> &'static str;
fn doc_long(&self) -> &'static str;
fn doc_choices(&self) -> &'static [&'static str] { &[] }
fn completion_type(&self) -> CompletionType { CompletionType::Other }
fn update(&self, value: FlagValue, args: &mut LowArgs) -> anyhow::Result<()>;
}The interesting ones:
name_long: every flag has a long name (>=2 bytes).name_short: optional one-byte short name.name_negated: if set, this flag also accepts a "no-" form (--no-line-numberfor--line-number, etc.).doc_shortanddoc_long: short text forrg --help, long text in mdoc/mdoc-style for the man page.doc_category: which--helpsection the flag lands in.update: receives the parsed value and updatesLowArgs. The convention is to do only parsing/validation here, not heavy work (e.g.,--hostname-bindoesn't run the binary; that happens later).
Category
enum Category {
Input, // patterns, haystacks, stdin
Search, // -i, --multiline, -w, ...
Filter, // .gitignore, file types, -g, --hidden, ...
Output, // line numbers, columns, colors, hyperlinks
OutputModes, // -c/--count, -l/--files-with-matches, --json, --files
Logging, // --debug, --trace, --quiet, --stats
OtherBehaviors, // --type-list, --pcre2-version, --generate
}rg --help and the man page group flags by category.
Negation flags
Almost every "switch" flag has a negation. The negation does not require its own struct:
fn name_negated(&self) -> Option<&'static str> { Some("no-line-number") }The parser routes --no-line-number to the same Flag::update with FlagValue::Switch(false).
LowArgs vs. HiArgs
This is the crucial layering:
LowArgs(crates/core/flags/lowargs.rs): flat struct with one field per flag. Filled in directly byFlag::update. Knows nothing about config files, environment, or cross-flag interactions.HiArgs(crates/core/flags/hiargs.rs): the resolved struct. Built fromLowArgsviaHiArgs::from_low_args. Resolves things like:- "case sensitivity" = combination of
-i,-S,--case-sensitive. - "threads to use" =
-j, fall back to num CPUs, force to 1 if--sortis set. - "stdout writer" = picks line-buffered or block-buffered termcolor stream based on whether stdout is a tty.
- "matcher" = constructs a
RegexMatcheror PCRE2 matcher with all the right options.
- "case sensitivity" = combination of
HiArgs is the API that crates/core/main.rs and crates/core/search.rs use. LowArgs is internal.
Config file
RIPGREP_CONFIG_PATH=/path/to/config makes ripgrep parse that file before argv. Each line of the file is treated as one argv element:
# my-config
--smart-case
--hidden
--no-messages
--colors=path:fg:cyanImplementation in crates/core/flags/config.rs. Lines starting with # are comments. Equivalent to passing those flags on the command line; argv overrides config-file values for non-cumulative flags.
--no-config skips the config file. RIPGREP_CONFIG_PATH= (empty) also skips.
--generate modes
The same Flag registry powers four generators:
| Mode | Flag | Output |
|---|---|---|
| Man page | --generate man |
roff source for man rg |
| Bash completion | --generate complete-bash |
complete -F _rg rg script |
| Zsh completion | --generate complete-zsh |
_rg zsh function |
| Fish completion | --generate complete-fish |
complete --command rg ... |
| PowerShell completion | --generate complete-powershell |
Register-ArgumentCompleter block |
The man page generator is crates/core/flags/doc/man.rs. It walks the flag registry, sorts by category, and emits mdoc-style sections. The completion generators are in crates/core/flags/complete/{bash,zsh,fish,powershell}.rs.
The 15.0.0 release added improvements for --hyperlink-format completions in bash/fish (FEATURE #3096) and zsh (FEATURE #3102), and made fish completions take config files into account (FEATURE #2708).
Parsing details
crates/core/flags/parse.rs uses lexopt to tokenise argv. Tokenisation is straightforward, but the dispatch step is interesting:
- Long flags hash-lookup by their
name_long()plusaliases(). - Short flags hash-lookup by their
name_short(). - Negations are recognised when a long flag starts with
--no-(and the matching positive flag has aname_negated).
The ParseResult<T> enum encodes the three outcomes:
ParseResult::Ok(args)— normal parse.ParseResult::Err(err)— parse failed.ParseResult::Special(SpecialMode::HelpShort)etc. — short-circuit for--help,--version,--pcre2-version. Skips the rest of initialisation so--helpalways works even if the working directory is broken.
Reference: file & function pointers
| What | Where |
|---|---|
Flag trait + Category enum |
crates/core/flags/mod.rs |
| All flag definitions (~235K) | crates/core/flags/defs.rs |
| Parser | crates/core/flags/parse.rs |
| Config-file reader | crates/core/flags/config.rs |
LowArgs |
crates/core/flags/lowargs.rs |
HiArgs |
crates/core/flags/hiargs.rs |
| Man page generator | crates/core/flags/doc/man.rs |
--help short / long generators |
crates/core/flags/doc/help.rs |
| Version generator | crates/core/flags/doc/version.rs |
| Bash / zsh / fish / PowerShell completion generators | crates/core/flags/complete/ |
Adding a new flag
- Add a struct to
defs.rs:struct MyFlag;andimpl Flag for MyFlag { ... }. - Register it in the master
FLAGSslice at the bottom of the file. - Add a field to
LowArgsfor the parsed value. - If the flag affects search behaviour, plumb it into
HiArgs::from_low_args. - Wire it into wherever the corresponding subsystem reads the value (matcher, searcher, walker, printer).
- Add an integration test in
tests/feature.rscovering both the flag and (if applicable) its negation. - Add a
CHANGELOG.mdbullet.
For style conventions see patterns and conventions.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.