Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ members = [
"benches/shadows/aube",
"benches/shadows/tak",
"benches/shadows/communique",
"benches/shadows/external-fd",
"benches/shadows/external-tokei",
"benches/shadows/external-starship",
]

# `benches/shadows/*` are generated by `mise run gen-shadow` and checked in. They are
Expand Down
24 changes: 14 additions & 10 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -861,14 +861,18 @@ feature list is not an exhaustive audit.
- [x] **Combination and stateful tests.** Focused typed conformance cases pair
defaults with env and delimiters, optional values with `require_equals`,
globals with overrides, subcommands with required positionals, groups with
defaults, and help/version collisions. Add `update_from` cases if that API is
implemented. Single-feature parity is not enough where clap resolves settings
in an order.
- [ ] **External clap adopters.** Before calling the replacement generally ready,
port or shadow at least three maintained, non-jdx clap CLIs chosen for
different shapes: a derive-heavy CLI, a builder-heavy CLI, and one using
custom validation/completions. Record every unsupported feature they expose;
do not silently narrow the sample to what already works.
defaults, and help/version collisions. `update_from` gets its own stateful
cases if that API is implemented. Single-feature parity is not enough where
settings resolve in an order.
- [x] **External clap adopters.** Source-derived probes pin and exercise three
maintained, non-jdx clap CLIs with different shapes: fd (derive-heavy),
tokei (builder-heavy), and starship (custom values/completions). fd's complete
57-flag clap command is captured and compiled as a typed usage shadow;
tokei's complete 18-flag builder command and starship's complete 15-command,
55-flag tree are captured and compile as typed shadows beside their reduced
executable probes. The audit in
`benches/external/README.md` records the unsupported surface instead of
silently dropping the remaining bridge losses and migration adaptations.
- [x] **Migration and non-goal documentation.** The Rust migration guide publishes an attribute mapping guide,
examples for the common `Parser` / `Args` / `Subcommand` / `ValueEnum`
rewrites, compile-fail examples for unsupported combinations, the clap
Expand Down Expand Up @@ -1401,8 +1405,8 @@ above are where it lands.
positionals.
- [x] **`license` metadata** (clap#1768) — `author`, `license`, and `repository`
are first-class typed root attributes, survive direct KDL emission, and render
in Markdown and manpages. GPL display requirements no longer need an
application-owned documentation patch.
in Markdown, manpages, and generated Go long help. GPL display requirements no
longer need an application-owned documentation patch.

Demand also attaches to boxes already open above: fixed arity with distinct
value names is clap#1717 + clap#1682 (31 votes combined); `Option` on a
Expand Down
52 changes: 50 additions & 2 deletions argv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1580,15 +1580,26 @@ impl<'t: 'v, 'a, 'v> Parser<'t, 'a, 'v> {
return self.step();
}

if is_negative_number(token)
// An exact declared short outranks the numeric shape. This keeps ordinary negative
// numbers available as values while allowing clap-compatible spellings such as fd's
// `-0` / `--print0` switch.
let declared_numeric_short = matches!(token, [b'-', short]
if short.is_ascii_digit() && self.find_short(*short).is_some());

if !declared_numeric_short
&& is_negative_number(token)
&& self
.next_arg()
.is_some_and(|arg| arg.allow_negative_numbers)
{
return Some(self.word(token));
}

if is_negative_number(token) && self.cmd.external_subcommand && !self.arg_filled {
if !declared_numeric_short
&& is_negative_number(token)
&& self.cmd.external_subcommand
&& !self.arg_filled
{
return Some(self.word(token));
}

Expand Down Expand Up @@ -2601,6 +2612,43 @@ mod tests {
);
}

#[test]
fn an_exact_declared_digit_short_outranks_a_negative_number() {
static PRINT0: Flag = Flag {
key: 92,
name: "print0",
shorts: b"0",
..Flag::BOOL
};
static VALUE: Arg = Arg {
key: 93,
name: "value",
required: false,
allow_negative_numbers: true,
..Arg::REQUIRED
};
static CMD: Command = Command {
name: "fd",
flags: &[&PRINT0],
args: &[&VALUE],
unknown_flags: Some(UnknownFlags::Error),
..Command::EMPTY
};

assert_eq!(
parse(&CMD, &argv(["-0"])),
Ok(vec![Event::Flag {
flag: &PRINT0,
value: None,
negated: false,
}])
);
assert!(matches!(
parse(&CMD, &argv(["-1"])),
Ok(events) if matches!(events.as_slice(), [Event::Arg { value: b"-1", .. }])
));
}

#[test]
fn negation_of_value_flag_does_not_consume_a_value() {
static MODE: Flag = Flag {
Expand Down
16 changes: 16 additions & 0 deletions benches/external/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# External clap adopter probes

These source-derived probes prevent the 6.x compatibility work from narrowing itself to jdx
CLIs. They are deliberately reduced rather than vendored: the tests retain each upstream's
distinctive declaration shapes, while this audit records everything the reduction cannot prove.

| Upstream | Pinned revision | Shape exercised | Migration result | Remaining upstream-specific gaps |
| --------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [sharkdp/fd](https://github.com/sharkdp/fd) | `ee20f426ddf338ac7ead5c5f00ea49258005caaf` | derive-heavy root, custom value parsers, hyphenated values, conflict sets | the complete 57-flag clap command is captured as KDL and compiles as a typed `usage-rs` shadow; the reduced probe agrees on accepted values and conflicts | three optional-value flags are reported as lossy by the clap bridge because clap exposes their arity but not their missing-value semantics; clap's `ArgGroup` builder expression must be expanded to field relationships; Elvish completion is outside the 6.0 shell set. The port also found fd's digit short `-0`; usage now gives an exact declared digit short precedence over the ordinary negative-number value rule |
| [XAMPPRocky/tokei](https://github.com/XAMPPRocky/tokei) | `fa44e5194060305576514d59b850353643afbfc8` | builder-only command and `ArgMatches`, append flags, choices, variadic positional | the complete 18-flag command is captured losslessly and compiles as a typed `usage-rs` shadow; the reduced probe exercises its interpreted bridge path | the builder/`ArgMatches` API is an intentional typed-parser non-goal; a real migration must either keep this cold bridge or rewrite the declaration as a typed struct; the custom clap colour palette is not portable |
| [starship/starship](https://github.com/starship/starship) | `6d38f35391a8e68952a3dd4b9acd40d3d93596f6` | subcommands, value-enum aliases, positional `requires`, generated completions | all 15 commands and 55 flags are captured and compile as a typed `usage-rs` shadow; the reduced probe agrees on alias binding and relationship failure | five flattened `pipestatus` occurrences report the same bridge arity loss because clap exposes `Append` and `num_args=1` but not the intended delimiter-expanded collection shape; computed version text must become explicit portable literals; Elvish remains outside the 6.0 shell set; primitive-returning parser functions need `FromStr` wrappers or portable validation |

The executable probes live in `conformance/tests/external_clap_adopters.rs`. Each pinned upstream's
complete captured command, structured bridge-loss report, and generated typed shadow live beside
this audit and under `benches/shadows/external-*`. Refresh this table and the checked-in captures
against a new upstream revision before adding or removing a recorded gap.
32 changes: 32 additions & 0 deletions benches/external/fd.losses.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
FidelityLoss {
command: [
"fd",
],
argument: Some(
"gen_completions",
),
feature: ValueArity,
detail: "num_args=0..=1, action=Set",
},
FidelityLoss {
command: [
"fd",
],
argument: Some(
"hyperlink",
),
feature: ValueArity,
detail: "num_args=0..=1, action=Set",
},
FidelityLoss {
command: [
"fd",
],
argument: Some(
"strip_cwd_prefix",
),
feature: ValueArity,
detail: "num_args=0..=1, action=Set",
},
]
Loading