Conversation
Experimental migration to the usage parser (usage-rs v6) for brush-core, brush-builtins, brush-experimental-builtins, and brush-shell: - builtins::Command now bounds on a new UsageParse trait bridging usage's inherent derive methods; impl_usage_parse! macro supplies the glue per parsed type - parse failures are rendered eagerly into an owned ParseError (usage errors borrow argv), preserving clap's exit-code contract (0 for help/version, 2 for failures) - strictness parity via unknown_flags/args_override_self where clap was strict; permissive mode where operands may look like flags (echo, test, printf, etc.) - printf gains a new() override keeping bash's post-format '--' as operand; set ±o uses Option<Option<String>> (single value per invocation, no accumulation) - xtask doc/completion generation emits the usage KDL spec; completion scripts come from usage's completions feature xtask and brush-test-harness intentionally remain on clap (dev tooling, not part of the shipped binary). Known deviations: custom help styles dropped (unsupported); override_usage literal only; set -o no longer accumulates repeated occurrences. Assisted-by: ox-alpha:opencode/x-preview-f-free
Under usage, SetOption's -o/+o fields were ported as Option<Option<String>>,
which made a second occurrence fail with DuplicateFlag. bash (and the
previous clap-based parser) accept repeated occurrences:
set -o nounset -o xtrace # both options applied
set +o posix +o allexport # both unset
Model each field as Vec<String> with default_missing = "": named
occurrences accumulate, while a bare -o/+o yields an empty-string entry
that triggers the list-all display, preserving both behaviors.
Assisted-by: ox-alpha:opencode/x-preview-f-free
|
Follow-up commit Implementation note for reviewers comparing parser expressiveness: usage models per-occurrence-optional values via #[usage(short = 'o', default_missing = "", value_name = "OPT")]
enable: Vec<String>, // "set -o" -> [""], "set -o a -o b" -> ["a", "b"]Verified: |
Public API changes for crate: brush-coreRemoved itemsAdded itemsChanged itemsPublic API changes for crate: brush-shellAdded itemsChanged itemsPerformance Benchmark Report
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is Test Summary: bash-completion test suite
|
The default_missing = "" spelling conflated a bare -o (list-all request) with 'set -o ""' (an invalid empty option name that must fail). Instead, SetCommand::new now rewrites bare occurrences to carry an unspellable sentinel value (BARE_OPTION), leaving explicit values untouched; execute() keys the list-all display off the sentinel. Also adds scripts/parser-parity.sh: a differential harness that runs a corpus of parsing-sensitive command lines through two brush builds and diffs stdout + exit status, so drift like this is detected mechanically rather than by review. Assisted-by: ox-alpha:opencode/x-preview-f-free
|
Follow-up commit
Detection: also added |
Two deterministic workloads exercising realistic builtin-parse density, verified for three-way output parity (bash vs clap build vs usage build): - deploy-sim.sh: getopts-driven deployment flow with directory bookkeeping, declarations, and formatted reporting - config-lint.sh: CI-style validator that re-parses a getopts option string per entry; the parse-densest case, and where the usage-based parser's end-to-end advantage is most visible Assisted-by: ox-alpha:opencode/x-preview-f-free
Real-world script benchmarksAdded
Reading: on scripts dominated by external commands or I/O ( So the earlier "hundreds of times faster" marketing number doesn't translate to shell startup, but for parse-heavy real-world scripts the improvement is material and scales with parse density. |
render_parse_error rendered help via the plain renderer, leaving the main CLI's --help unstyled even on a terminal while builtin content used render_styled. Use usage's auto styles instead: stdout-based for help requests, stderr-based for arg_required_else_help output. usage picks its own palette automatically and honors NO_COLOR / CLICOLOR_FORCE / per-stream tty detection; only custom clap-style palettes are non-portable. Assisted-by: ox-alpha:opencode/x-preview-f-free
Experiment: replace clap with usage-rs
Compares usage-rs (the Rust framework behind
usage-cli) against clap 4.6 as brush's argument-parsing layer. Kept as a separate branch/worktree so both parsers can be built and measured from the same sources.What was migrated
All shipped runtime crates now parse with
usage(v6):brush-core—builtins::Commandinfrastructure, completion value enums, examplebrush-builtins— all ~45 builtin argument structsbrush-experimental-builtinsbrush-shell— main CLI (args.rs,entry.rs,brushctl.rs,events.rs)Deliberately not migrated (dev tooling, not part of the shipped binary):
xtask,brush-test-harness.Notable design changes
builtins::Commandis no longer bounded onclap::Parser. A small newUsageParsetrait bridges parsed types; each builtin adds one line of glue viabrush_core::impl_usage_parse!(T)(brush-core/src/builtins.rs).builtins::ParseErrorthat preserves clap's exit-code contract (help/version → 0, failure → 2).unknown_flags = "error"+args_override_self = false. Builtins whose operands may legitimately look like flags (echo,test,printf,let,eval, …) use the permissive mode instead.-/+option trick still works unchanged (+x→--+x, long declared as"+x"); verified against usage's parser.set -o/+o: repeated occurrences accumulate like bash; bare-o/+o(list-all) is distinguished from an explicitly empty name via an unspellable sentinel attached duringSetCommand::new's existing argv rewrite pass.printfgained anew()override so a standalone--appearing after the format string stays an ordinary operand (bash semantics the parser can't express).NO_COLOR/CLICOLOR_FORCE/ per-stream tty detection. Only clap's custom palettes are non-portable.xtask gen man/markdownnow emit the CLI's usage KDL spec (renderable byusage-cli); completion scripts come from usage'scompletionsfeature.scripts/parser-parity.sh <ref> <cand>: differential harness running a corpus of parsing-sensitive command lines through two builds and diffing stdout + exit status (20 cases covering every drift class found during this experiment).Verification
cargo xtask ci quickgreen (fmt, clippy, unit tests)run_suspend_and_fg(flakes identically onmain; pty/job-control timing)scripts/parser-parity.sh: 20/20 vs a clap buildPerformance
Microbenchmarks (hyperfine, release builds):
brush -c 'exit 0'brush --helplet×3000)Real-world scripts (details and reproduction in #1301):
gnuconfig/config.guess(1818 ln)scripts/test-across-shells.shbenchmarks/real-world/deploy-sim.shbash_completion(3624 ln)benchmarks/real-world/config-lint.sh 250config-lint.sh 750Reading: end-to-end time on I/O- or external-command-bound scripts is indistinguishable between parsers; as builtin argument parsing becomes the bottleneck, usage shows a consistent win that grows with parse density (~1.1× → ~1.7×).
Known deviations / follow-ups
Styles::styled()with brush's yellow/green/magenta/cyan scheme) are not portable.override_usage(declare) dropped — usage only takes literal usage lines; colorized usage line lost.ulimit: dynamic "(supported)/(unsupported)" help suffixes became static text.exit/fcoperands: hyphen-leading non-numeric tokens now error instead of being captured (negative numbers still work).builtins::Commandbounds, removal ofparse_known,clap::Error→builtins::ParseErrorinCommand::new.Assisted-by: ox-alpha:opencode/x-preview-f-free