Skip to content

Commit a989d26

Browse files
authored
refactor(derive): remove clap compatibility spellings
## Summary - require `#[usage(...)]` metadata across usage CLI derives and emit targeted errors for legacy clap helper namespaces - reject clap-only inner synonyms with their native replacements and remove implicit clap group generation - migrate derive fixtures, conformance coverage, generated shadows, rustdoc, and migration documentation to native syntax ## Testing - `cargo test -p usage-derive` - `cargo test -p usage-rs --all-features --tests` - `cargo test -p usage-conformance --all-features` - `cargo test -p xtask` - `cargo test --all --all-features` - `mise run gen-shadow` - `mise run ci` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking public derive API: clap-compatible attributes and implicit Args groups no longer compile. Runtime parse behavior is otherwise unchanged. > > **Overview** > **Breaking:** usage CLI derives now accept only native `#[usage(...)]` metadata. `#[command]`, `#[arg]`, `#[value]`, and `#[group]` are still registered so they fail at the source span with a rewrite to `#[usage(...)]`. Inner clap synonyms (`id`, `default_value`, `conflicts_with`, `value_parser`, `last`, etc.) are rejected the same way. > > Implicit clap `#[group(...)]` generation is gone. Requiredness comes only from field types/`required`, not one-member implicit groups. > > Docs, README, conformance tests, and generated shadow CLIs are rewritten to native syntax. Clap migration examples are explicit before/after rewrites rather than “keep the old spelling.” > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 840fd41. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added comprehensive support for native `#[usage(...)]` CLI metadata across commands, arguments, values, aliases, groups, and subcommands. * Improved handling of required flags and argument groups, including single-member groups. * **Bug Fixes** * Legacy attribute spellings now provide clearer replacement guidance when unsupported. * **Documentation** * Updated migration and subcommand guides with native syntax and expanded behavior examples. * **Tests** * Updated conformance and compatibility coverage for native attribute behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 4bddddb commit a989d26

26 files changed

Lines changed: 970 additions & 1104 deletions

File tree

AGENTS.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# CLAUDE.md
1+
# AGENTS.md
22

3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3+
This file provides guidance to coding agents working in this repository.
44

55
## Conventional Commits
66

@@ -103,6 +103,16 @@ conformance, generation, and performance tooling:
103103

104104
## Architecture
105105

106+
### Rust Derives (`derive/`, `usage-rs/`)
107+
108+
The Rust framework uses `#[derive(Cli)]`, `Args`, `Subcommands`, `ValueEnum`, and
109+
`ArgGroup` to compile typed declarations into argv parse tables and portable spec
110+
metadata. All derive metadata uses the native `#[usage(...)]` helper attribute.
111+
Legacy clap helper namespaces such as `#[command(...)]`, `#[arg(...)]`,
112+
`#[value(...)]`, and `#[group(...)]` are rejected with migration diagnostics; do
113+
not add compatibility spellings back. Keep clap migration examples as explicit
114+
before/after rewrites to `#[usage(...)]`.
115+
106116
### Spec Model (`lib/src/spec/`)
107117

108118
The spec model represents a CLI definition parsed from KDL:

README.md

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,53 @@
11
# Usage
22

3-
Usage is a spec and CLI for defining CLI tools. Arguments, flags, environment variables, and config files
4-
can all be defined in a Usage spec. It can be thought of like [OpenAPI (swagger)](https://www.openapis.org/)
5-
for CLIs. Here are some potential reasons for defining your CLI with a Usage spec:
3+
Usage is a spec, CLI, and Rust framework for defining command-line interfaces.
4+
Arguments, flags, environment variables, and config files can all be described in
5+
a portable KDL spec. Think of it as [OpenAPI](https://www.openapis.org/) for CLIs:
6+
one declaration can drive parsing and every user-facing artifact.
67

7-
- Generate autocompletion scripts
8-
- Generate markdown documentation
9-
- Generate man pages
10-
- Use an advanced arg parser in any language
11-
- Scaffold one spec into different CLI frameworks—even different languages
12-
- [coming soon] Host your CLI documentation on usage.sh
8+
- Generate shell completions
9+
- Generate Markdown documentation and man pages
10+
- Parse arguments from any language
11+
- Scaffold a spec into CLI frameworks in different languages
12+
- Build a typed Rust CLI with a zero-dependency runtime
1313

1414
See more at [usage.jdx.dev](https://usage.jdx.dev/).
1515

16+
## Rust framework
17+
18+
Applications can use `usage-rs` to derive a typed parser and a portable Usage
19+
spec from the same Rust declaration:
20+
21+
```toml
22+
[dependencies]
23+
usage = { package = "usage-rs", version = "6" }
24+
```
25+
26+
```rust
27+
use usage::Cli;
28+
29+
#[derive(Cli)]
30+
#[usage(bin = "example", version)]
31+
struct App {
32+
/// Print more detail.
33+
#[usage(short = 'v', long, count)]
34+
verbose: u8,
35+
36+
/// Files to process.
37+
files: Vec<String>,
38+
}
39+
40+
fn main() {
41+
let app = App::parse();
42+
// app.verbose and app.files are ready to use
43+
}
44+
```
45+
46+
Usage has its own derive vocabulary: use `#[usage(...)]` on commands, fields,
47+
and value variants. See the [Rust framework guide](https://usage.jdx.dev/rust/)
48+
and [clap migration guide](https://usage.jdx.dev/rust/migrating-from-clap) for
49+
the supported mappings and intentional differences.
50+
1651
## Sponsors
1752

1853
usage is sponsored by [entire.io](https://entire.io) and [37signals](https://37signals.com).
@@ -21,10 +56,10 @@ usage is sponsored by [entire.io](https://entire.io) and [37signals](https://37s
2156

2257
## Acknowledgements
2358

24-
Usage's design owes a great deal to [clap](https://github.com/clap-rs/clap) — the
25-
derive attribute vocabulary, the help output shape, and the diagnostic conventions
26-
all follow it deliberately so clap CLIs can be ported field by field. clap's
27-
license is reproduced in [NOTICE.md](NOTICE.md).
59+
Usage's design owes a great deal to [clap](https://github.com/clap-rs/clap). Its
60+
help output and diagnostic conventions make clap migrations familiar, while
61+
Usage's native derive attributes reflect its portable spec model. clap's license
62+
is reproduced in [NOTICE.md](NOTICE.md).
2863

2964
## License
3065

benches/shadows/aube/src/lib.rs

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -536,25 +536,25 @@ pub struct AuditArgs {
536536

537537
#[derive(ValueEnum)]
538538
pub enum AuditAuditLevelValue {
539-
#[value(name = "info")]
539+
#[usage(name = "info")]
540540
Info,
541-
#[value(name = "low")]
541+
#[usage(name = "low")]
542542
Low,
543-
#[value(name = "moderate")]
543+
#[usage(name = "moderate")]
544544
Moderate,
545-
#[value(name = "high")]
545+
#[usage(name = "high")]
546546
High,
547-
#[value(name = "critical")]
547+
#[usage(name = "critical")]
548548
Critical,
549549
}
550550

551551
#[derive(ValueEnum)]
552552
pub enum AuditFixValue {
553553
/// Refresh the lockfile to patched versions allowed by existing ranges.
554-
#[value(name = "update")]
554+
#[usage(name = "update")]
555555
Update,
556556
/// Write package.json overrides that force patched versions.
557-
#[value(name = "override")]
557+
#[usage(name = "override")]
558558
Override,
559559
}
560560

@@ -904,13 +904,13 @@ pub struct ConfigDeleteArgs {
904904
#[derive(ValueEnum)]
905905
pub enum ConfigDeleteLocationValue {
906906
/// User config (`~/.config/aube/config.toml` for known aube settings, `~/.npmrc` for registry/auth and unknown keys)
907-
#[value(name = "user")]
907+
#[usage(name = "user")]
908908
User,
909909
/// `<cwd>/.npmrc`
910-
#[value(name = "project")]
910+
#[usage(name = "project")]
911911
Project,
912912
/// Alias for `user` — aube has no separate global config file.
913-
#[value(name = "global")]
913+
#[usage(name = "global")]
914914
Global,
915915
}
916916

@@ -964,16 +964,16 @@ pub struct ConfigGetArgs {
964964
#[derive(ValueEnum)]
965965
pub enum ConfigGetLocationValue {
966966
/// Merge `~/.npmrc`, user aube config, and project `.npmrc`, last-write-wins (same precedence install uses).
967-
#[value(name = "merged")]
967+
#[usage(name = "merged")]
968968
Merged,
969969
/// Only user config (`~/.config/aube/config.toml` + `~/.npmrc`)
970-
#[value(name = "user")]
970+
#[usage(name = "user")]
971971
User,
972972
/// Only `<cwd>/.npmrc`
973-
#[value(name = "project")]
973+
#[usage(name = "project")]
974974
Project,
975975
/// Alias for `user`.
976-
#[value(name = "global")]
976+
#[usage(name = "global")]
977977
Global,
978978
}
979979

@@ -1008,16 +1008,16 @@ pub struct ConfigListArgs {
10081008
#[derive(ValueEnum)]
10091009
pub enum ConfigListLocationValue {
10101010
/// Merge `~/.npmrc`, user aube config, and project `.npmrc`, last-write-wins (same precedence install uses).
1011-
#[value(name = "merged")]
1011+
#[usage(name = "merged")]
10121012
Merged,
10131013
/// Only user config (`~/.config/aube/config.toml` + `~/.npmrc`)
1014-
#[value(name = "user")]
1014+
#[usage(name = "user")]
10151015
User,
10161016
/// Only `<cwd>/.npmrc`
1017-
#[value(name = "project")]
1017+
#[usage(name = "project")]
10181018
Project,
10191019
/// Alias for `user`.
1020-
#[value(name = "global")]
1020+
#[usage(name = "global")]
10211021
Global,
10221022
}
10231023

@@ -1051,13 +1051,13 @@ pub struct ConfigSetArgs {
10511051
#[derive(ValueEnum)]
10521052
pub enum ConfigSetLocationValue {
10531053
/// User config (`~/.config/aube/config.toml` for known aube settings, `~/.npmrc` for registry/auth and unknown keys)
1054-
#[value(name = "user")]
1054+
#[usage(name = "user")]
10551055
User,
10561056
/// `<cwd>/.npmrc`
1057-
#[value(name = "project")]
1057+
#[usage(name = "project")]
10581058
Project,
10591059
/// Alias for `user` — aube has no separate global config file.
1060-
#[value(name = "global")]
1060+
#[usage(name = "global")]
10611061
Global,
10621062
}
10631063

@@ -1099,16 +1099,16 @@ pub struct ConfigArgs {
10991099
#[derive(ValueEnum)]
11001100
pub enum ConfigLocationValue {
11011101
/// Merge `~/.npmrc`, user aube config, and project `.npmrc`, last-write-wins (same precedence install uses).
1102-
#[value(name = "merged")]
1102+
#[usage(name = "merged")]
11031103
Merged,
11041104
/// Only user config (`~/.config/aube/config.toml` + `~/.npmrc`)
1105-
#[value(name = "user")]
1105+
#[usage(name = "user")]
11061106
User,
11071107
/// Only `<cwd>/.npmrc`
1108-
#[value(name = "project")]
1108+
#[usage(name = "project")]
11091109
Project,
11101110
/// Alias for `user`.
1111-
#[value(name = "global")]
1111+
#[usage(name = "global")]
11121112
Global,
11131113
}
11141114

@@ -2134,16 +2134,16 @@ pub struct GetArgs {
21342134
#[derive(ValueEnum)]
21352135
pub enum GetLocationValue {
21362136
/// Merge `~/.npmrc`, user aube config, and project `.npmrc`, last-write-wins (same precedence install uses).
2137-
#[value(name = "merged")]
2137+
#[usage(name = "merged")]
21382138
Merged,
21392139
/// Only user config (`~/.config/aube/config.toml` + `~/.npmrc`)
2140-
#[value(name = "user")]
2140+
#[usage(name = "user")]
21412141
User,
21422142
/// Only `<cwd>/.npmrc`
2143-
#[value(name = "project")]
2143+
#[usage(name = "project")]
21442144
Project,
21452145
/// Alias for `user`.
2146-
#[value(name = "global")]
2146+
#[usage(name = "global")]
21472147
Global,
21482148
}
21492149

@@ -2625,11 +2625,11 @@ pub struct LaArgs {
26252625

26262626
#[derive(ValueEnum)]
26272627
pub enum LaFormatValue {
2628-
#[value(name = "default")]
2628+
#[usage(name = "default")]
26292629
Default,
2630-
#[value(name = "json")]
2630+
#[usage(name = "json")]
26312631
Json,
2632-
#[value(name = "parseable")]
2632+
#[usage(name = "parseable")]
26332633
Parseable,
26342634
}
26352635

@@ -2766,11 +2766,11 @@ pub struct ListArgs {
27662766

27672767
#[derive(ValueEnum)]
27682768
pub enum ListFormatValue {
2769-
#[value(name = "default")]
2769+
#[usage(name = "default")]
27702770
Default,
2771-
#[value(name = "json")]
2771+
#[usage(name = "json")]
27722772
Json,
2773-
#[value(name = "parseable")]
2773+
#[usage(name = "parseable")]
27742774
Parseable,
27752775
}
27762776

@@ -2825,11 +2825,11 @@ pub struct LlArgs {
28252825

28262826
#[derive(ValueEnum)]
28272827
pub enum LlFormatValue {
2828-
#[value(name = "default")]
2828+
#[usage(name = "default")]
28292829
Default,
2830-
#[value(name = "json")]
2830+
#[usage(name = "json")]
28312831
Json,
2832-
#[value(name = "parseable")]
2832+
#[usage(name = "parseable")]
28332833
Parseable,
28342834
}
28352835

@@ -3916,9 +3916,9 @@ pub struct SbomArgs {
39163916

39173917
#[derive(ValueEnum)]
39183918
pub enum SbomFormatValue {
3919-
#[value(name = "cyclonedx")]
3919+
#[usage(name = "cyclonedx")]
39203920
Cyclonedx,
3921-
#[value(name = "spdx")]
3921+
#[usage(name = "spdx")]
39223922
Spdx,
39233923
}
39243924

@@ -4009,13 +4009,13 @@ pub struct SetArgs {
40094009
#[derive(ValueEnum)]
40104010
pub enum SetLocationValue {
40114011
/// User config (`~/.config/aube/config.toml` for known aube settings, `~/.npmrc` for registry/auth and unknown keys)
4012-
#[value(name = "user")]
4012+
#[usage(name = "user")]
40134013
User,
40144014
/// `<cwd>/.npmrc`
4015-
#[value(name = "project")]
4015+
#[usage(name = "project")]
40164016
Project,
40174017
/// Alias for `user` — aube has no separate global config file.
4018-
#[value(name = "global")]
4018+
#[usage(name = "global")]
40194019
Global,
40204020
}
40214021

@@ -5245,29 +5245,29 @@ pub struct Cli {
52455245

52465246
#[derive(ValueEnum)]
52475247
pub enum LoglevelValue {
5248-
#[value(name = "trace")]
5248+
#[usage(name = "trace")]
52495249
Trace,
5250-
#[value(name = "debug")]
5250+
#[usage(name = "debug")]
52515251
Debug,
5252-
#[value(name = "info")]
5252+
#[usage(name = "info")]
52535253
Info,
5254-
#[value(name = "warn")]
5254+
#[usage(name = "warn")]
52555255
Warn,
5256-
#[value(name = "error")]
5256+
#[usage(name = "error")]
52575257
Error,
5258-
#[value(name = "silent")]
5258+
#[usage(name = "silent")]
52595259
Silent,
52605260
}
52615261

52625262
#[derive(ValueEnum)]
52635263
pub enum ReporterValue {
5264-
#[value(name = "default")]
5264+
#[usage(name = "default")]
52655265
Default,
5266-
#[value(name = "append-only")]
5266+
#[usage(name = "append-only")]
52675267
AppendOnly,
5268-
#[value(name = "ndjson")]
5268+
#[usage(name = "ndjson")]
52695269
Ndjson,
5270-
#[value(name = "silent")]
5270+
#[usage(name = "silent")]
52715271
Silent,
52725272
}
52735273

0 commit comments

Comments
 (0)