Conversation
Replace clap with bpaf 0.9 across the runtime crates (brush-core,
brush-builtins, brush-experimental-builtins, brush-shell, and the test
harness), reworking the builtin Command trait around bpaf's parser
combinators.
Core changes:
- builtins::Command now requires a bpaf 'parser()' plus 'about()' /
'synopsis()' metadata used by the help builtin; detailed help is
rendered by triggering bpaf's --help handling
- add bash-faithful option/operand splitting (split_option_section) for
builtins that capture trailing operands verbatim, replacing the
per-builtin clap workarounds ('--' splitting, argv0 handling)
- plus-style options (+x, +o): expand '+abc' groups like the shell does
and match them with bpaf 'literal' parsers; drop the old '+x' ->
'--+x' encoding, so literal '--+o' input is now rejected like bash
- ValueEnum types become FromStr-based (completion actions/options,
trace events, input backends)
Shell CLI:
- rewrite CommandLineArgs as a combinatoric parser; bash-compatible
handling of '-c' with '--', script arguments captured verbatim after
the option section
Builtins: port all ~50 builtins; several now behave closer to bash
(echo preserves '--', kill -l no longer swallows signals, pwd -LP
last-wins, set -- clears positional parameters, printf/command honor
leading '--').
Dev tooling: xtask doc generation renders help via bpaf (clap_mangen/
clap_markdown/clap_complete dropped); completion scripts are generated
by running the brush binary's own bpaf completion support.
Assisted-by: ox-alpha (opencode)
Public API changes for crate: brush-builtinsRemoved itemsPublic API changes for crate: brush-coreRemoved itemsAdded itemsChanged itemsPublic API changes for crate: brush-shellRemoved itemsAdded itemsChanged itemsPublic API changes for crate: brush-test-harnessRemoved itemsAdded itemsChanged itemsPerformance Benchmark ReportCode Coverage Report: Only Changed Files listed
Minimum allowed coverage is Test Summary: bash-completion test suite
|
Adds a self-contained benchmark driver comparing two brush builds against a bash oracle: - verifies byte-for-byte output parity across all three shells before timing anything - interleaves samples round-robin so machine drift affects every shell equally (sequential test-vs-reference runs proved drift-sensitive on multi-tenant machines) - reports median +- MAD wall time, pairwise speedups, and per-workload peak RSS (VmHWM) Workloads: process startup, pure interpreter loop, string/pattern/array ops, and the getopts-dense config-lint/deploy-sim scripts (copied from the usage-parser experiment). Assisted-by: ox-alpha (opencode)
The first listed shell acts as the oracle (bash); every other entry is a candidate build. Adds an all-pairs comparison section so parser-backend and LTO-variant matrices can be captured in one run. Assisted-by: ox-alpha (opencode)
Benchmark: bash vs {clap, bpaf, usage} × {fat-LTO, thin-LTO}Interleaved, pinned sampling ( Wall time (ms, median)
TakeawaysParser backends (config-lint, the parse-densest workload):
The ranking (usage < bpaf < clap) is consistent and well outside the ±0.5% noise floor. LTO: fat ≈ thin everywhere. Within each backend the two variants sit within 1–3% of each other on every workload, with fat slightly ahead more often than not. No evidence that LTO flavor is a meaningful lever here — and notably, an earlier apparent "+35% pure-interp regression" for bpaf did not reproduce under this harness (interp-loop: clap-fat 273 ms vs bpaf-fat 285 ms, ~+4%), indicating it was a measurement artifact of sequential test-vs-reference sampling on a multi-tenant machine rather than real codegen cost. Other observations:
Reproduce with: |
Problem is 0.10 should expose a bit more primitives so you can implement some bits, but even then dealing with global parsers will require building the whole context.
0.10 adds
Hmm... Isn't it what https://docs.rs/bpaf/latest/bpaf/trait.Parser.html#method.last
I don't think I understand this :)
metavar is needed to indicate that something is an argument (an option-argument if you use Open group terminology). I'm not sure how ability to change that. Do you have any examples where this can be used?
Hmm... https://docs.rs/bpaf/latest/bpaf/doc/index.html, requires docgen feature or something like that. Changed a bit in 0.10, but it should still be able to produce roff/markdown.
Hmmm... Yeah, I can add that. |
clap allows to have aliases, and bpaf apparently not in a as straightforward way, but I can be possibly wrong. |
Each named item can have two visible names (first short and first long) and as many hidden aliases as you want. |
bpaf supports multiple names per named item via repeated .long()/.short() calls: the first short and first long are visible in help, further ones act as hidden aliases. Replaces the construct![a, b] alternative pairs used for --init-file/--rcfile, --log-enable/--debug, --log-disable/--disable-event, and kill's -L/-l with single parsers. This removes duplicated parser definitions, keeps help output showing only the canonical spellings (matching the clap-era help), and lets kill accept -L again as a hidden alias of -l. Reported-by: reubeno (upstream author feedback) Assisted-by: ox-alpha (opencode)
Assisted-by: ox-alpha (opencode)
|
Updated, thank you :) |
…ed deps The TestOptions port to bpaf lost several clap 'env = ...' fallbacks, which broke the WASI test job: BRUSH_PLATFORM_TAGS was no longer read, so the harness appended --input-backend=basic to a minimal-feature wasm build that does not contain that backend; brush rejected the unknown option and every integration case failed with exit 1. Restore all environment fallbacks using bpaf's native .env() support (BRUSH_VERBOSE, BASH_PATH, BRUSH_PATH, BRUSH_ARGS, BRUSH_LAUNCHER, BRUSH_PLATFORM_TAGS, BRUSH_TEST_CASES, BRUSH_TEST_PATH_VAR). For BRUSH_PLATFORM_TAGS, whose value is space-separated, use '.some()' plus a splitting parse rather than '.many()': many() succeeds with an empty vector, which would prevent the fallback from ever applying. Also remove dependencies left unused by the port (pretty_assertions in brush-shell after its tests were rewritten, clap-markdown in xtask). Assisted-by: ox-alpha (opencode)
Render shell CLI parse failures through bpaf's print_message (respecting NO_COLOR / terminal support) unless --disable-color was requested, and finish the harness TestOptions env-var fallbacks (--bash-path via BASH_PATH env with CLI precedence). Assisted-by: ox-alpha (opencode)
Draft PR exploring a port of brush's argument parsing from clap to bpaf (0.9), linked to #520.
What this does
Replaces clap with bpaf in the runtime crates (
brush-core,brush-builtins,brush-experimental-builtins,brush-shell,brush-test-harness), redesigning the builtinCommandtrait around bpaf parser combinators:builtins::Commandnow requiresfn parser() -> impl bpaf::Parser<Self> + 'staticplus small metadata hooks (about(),synopsis()) that feed thehelpbuiltin. Detailed help is rendered by triggering bpaf's own--helphandling.builtins::split_option_section) decides where the option section ends for builtins with verbatim trailing operands (echo,test,set,fc, ...). Builtins declare their value-taking short options; the splitter honors--, first-operand termination, and attached values (-d:,-G=--foo).set +x,declare +a) no longer use the old clap-era+x → --+xencoding:+abcgroups are expanded the way the shell tokenizes them and matched with bpafliteral("+x")parsers. As a bonus, literal--+o fooinput — which bash rejects and the old encoding silently accepted as+o— is now rejected like bash.clap::ValueEnumtypes become plainFromStrenums (completion actions/options, trace events, input backends).-c/--handling; script arguments are captured verbatim after the option section.long("rcfile").long("init-file"),short('l').short('L')): first spellings visible in help, rest hidden aliases.Behavior notes
Several builtins now behave closer to bash than before:
echo -- -n zprints-n z;echo ----------------echoes the dasheskill -s TERM $$,kill -9 $$,kill -Lwork again under the harnesspwd -L -P/pwd -P -L: last flag winsset --clears positional parameters;test --/[ -- ]are trueprintf -- -5andcommand -- lshonor the terminatorValidation
/usr/share/bash-completion), integration tests passcargo fmt --checkandcargo clippy --workspace --all-targets -D warningscleanbenchmarks/three-way.pyNice-to-have bpaf API changes
run_inner(["--help"])and unwrapping aParseFailure. AnOptionParser::render_help_string()(monochrome + colored variants) would remove the round-trip.echo hi -nmust keep-nliteral) cannot be expressed combinatorially today; we pre-split argv ourselves. Something like a positional parser withallow_hyphen_values-plus-trailing_var_argsemantics would let us delete the splitter.overrides_withpattern (cd -P -L) can't be expressed; alternatives conflict and independent switches lose ordering.clap_mangen/clap_markdown. An official (or documented third-party) way to render bpaf parsers to roff/markdown would restore richer doc generation; xtask currently emits from monochrome help text.ParseFailure: std::error::Error(or aninto_error()) so?-style plumbing works in test code without manual matching.(An earlier version of this list claimed aliases were unsupported; that was wrong — chained
.long()/.short()provide hidden aliases, now used throughout.)Follow-ups (not in this draft)
xtask's own CLI and any remaining dev-tool CLIs to bpafContentOptions::colorizedonce colorized rendering is reachable