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
9 changes: 5 additions & 4 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ Groups are the opposite case: `Command::get_groups`, `ArgGroup::get_args` and
`disable_help_subcommand` and `disable_version_flag`. A clap
CLI can move or remove these entry points; usage currently supplies its own.
- [ ] **Ordering and remaining metadata** — explicit `display_order`, the full
`ValueHint` vocabulary, `author`, `long_version`, and custom help/version
`ValueHint` vocabulary, `long_version`, and custom help/version
text. Declaration order and path hints cover the common cases, not the
complete clap surface.

Expand Down Expand Up @@ -1337,9 +1337,10 @@ above are where it lands.
resolves it (the parser already gives `--color bar`'s `bar` to the
positionals) — so the recommended declaration is `default_missing` with
`require_equals` beside it.
- [ ] **`license` metadata** (clap#1768) — a spec node rendered into manpages
and docs. GPL display requirements want it, and it rounds out the
manpage story.
- [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.

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
21 changes: 21 additions & 0 deletions argv/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ pub struct Spec<'a> {
/// The binary as invoked, when it differs from `name`.
pub bin: Option<&'a str>,
pub version: Option<&'a str>,
/// Package authorship text for generated references.
pub author: Option<&'a str>,
/// SPDX expression or other license label for generated references.
pub license: Option<&'a str>,
/// Source repository URL for generated references and integrations.
pub repository: Option<&'a str>,
/// The oldest `usage` that can read this spec, when the CLI says.
///
/// Written first, before anything a `usage` too old to understand would choke on — which is
Expand Down Expand Up @@ -472,6 +478,9 @@ impl<'a> SpecView<'a> {
None => self.base.version,
}
},
author: self.base.author,
license: self.base.license,
repository: self.base.repository,
min_usage_version: self.base.min_usage_version,
about: self.base.about,
long_about: self.base.long_about,
Expand All @@ -497,6 +506,9 @@ impl Spec<'_> {
name: "",
bin: None,
version: None,
author: None,
license: None,
repository: None,
min_usage_version: None,
about: None,
long_about: None,
Expand Down Expand Up @@ -1135,6 +1147,15 @@ impl Spec<'_> {
if let Some(version) = self.version {
prop(out, "version", version)?;
}
if let Some(author) = self.author {
prop(out, "author", author)?;
}
if let Some(license) = self.license {
prop(out, "license", license)?;
}
if let Some(repository) = self.repository {
prop(out, "repository", repository)?;
}
// A description may be given on the spec or on its root command — a derive
// naturally has one doc comment and no reason to care which field it lands
// in — so either is written, the spec's first.
Expand Down
2 changes: 2 additions & 0 deletions cli/assets/usage.1
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,5 @@ Arguments to pass to script

Anything `usage` does not recognise is a value rather than a mistake, which is what
lets a shebang script take flags of its own.
.SH SOURCE
https://github.com/jdx/usage
3 changes: 3 additions & 0 deletions conformance/src/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ pub fn build_spec(spec: &Spec) -> &'static usage_argv::spec::Spec<'static> {
name: leak(&spec.name),
bin: Some(leak(&spec.bin)),
version: opt(&spec.version),
author: opt(&spec.author),
license: opt(&spec.license),
repository: opt(&spec.repository),
min_usage_version: opt(&spec.min_usage_version),
about: opt(&spec.about),
long_about: opt(&spec.about_long),
Expand Down
12 changes: 12 additions & 0 deletions conformance/tests/spec_roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ static SPEC: Spec = Spec {
name: "ex",
bin: Some("ex"),
version: Some("1.2.3"),
author: None,
license: None,
repository: None,
min_usage_version: None,
about: Some("does things"),
long_about: Some("Does things, at length."),
Expand Down Expand Up @@ -759,6 +762,9 @@ fn a_declared_completer_becomes_a_run_the_reference_can_read() {
name: "ex",
bin: Some("ex"),
version: None,
author: None,
license: None,
repository: None,
min_usage_version: None,
about: None,
long_about: None,
Expand Down Expand Up @@ -892,6 +898,9 @@ fn two_commands_can_mean_different_things_by_one_name() {
name: "ex",
bin: Some("ex"),
version: None,
author: None,
license: None,
repository: None,
min_usage_version: None,
about: None,
long_about: None,
Expand Down Expand Up @@ -946,6 +955,9 @@ fn two_commands_can_mean_different_things_by_one_name() {
name: "ex",
bin: Some("ex"),
version: None,
author: None,
license: None,
repository: None,
min_usage_version: None,
about: None,
long_about: None,
Expand Down
6 changes: 6 additions & 0 deletions derive/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ pub fn emit(cli: &Cli) -> TokenStream {
Some(tokens) => quote!(::core::option::Option::Some(#tokens)),
None => quote!(::core::option::Option::None),
};
let author = option_expr(cli.author.as_ref());
let license = option_expr(cli.license.as_ref());
let repository = option_expr(cli.repository.as_ref());
Comment thread
cursor[bot] marked this conversation as resolved.
let about = cli
.about_attr
.as_ref()
Expand Down Expand Up @@ -626,6 +629,9 @@ pub fn emit(cli: &Cli) -> TokenStream {
name: #name,
bin: #bin,
version: #version,
author: #author,
license: #license,
repository: #repository,
min_usage_version: #min_usage_version,
about: #about,
long_about: #long_about,
Expand Down
3 changes: 2 additions & 1 deletion derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@
//! spelling the CLI declares for itself. clap refuses that collision by panicking at startup;
//! here the declaration simply wins and the other spelling still answers.
//!
//! On the struct itself: `bin`, `version`, `about`, `long_about`, `before_help`, `after_help`,
//! On the struct itself: `bin`, `version`, `author`, `license`, `repository`, `about`,
//! `long_about`, `before_help`, `after_help`,
//! `verbatim_doc_comment` — preserve doc-comment line breaks and whitespace —
//! `default_subcommand`, `multicall` — argv[0]'s basename selects a subcommand —
//! `arg_required_else_help` — a selected command with no argv of its own shows short help —
Expand Down
54 changes: 53 additions & 1 deletion derive/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ pub struct Cli {
/// From the struct's doc comment: first paragraph, and the whole thing.
pub about: Option<String>,
pub long_about: Option<String>,
/// Package metadata expressions whose results must be usable as `&'static str`.
///
/// Keeping their tokens lets the Cargo-provided `env!("CARGO_PKG_…")` values remain the
/// source of truth instead of making adopters repeat those strings in the derive attribute.
pub author: Option<proc_macro2::TokenStream>,
pub license: Option<proc_macro2::TokenStream>,
pub repository: Option<proc_macro2::TokenStream>,
/// Whether a flag-like token that names no flag is a value or an error. Unset
/// means the spec's default, which is `value`.
pub unknown_flags: Option<String>,
Expand Down Expand Up @@ -562,6 +569,9 @@ impl Cli {
runtime_version: None,
about: None,
long_about: None,
author: None,
license: None,
repository: None,
unknown_flags: None,
default_subcommand: None,
multicall: false,
Expand Down Expand Up @@ -687,6 +697,9 @@ impl Cli {
// declared.
"about" => cli.about_attr = Some(metadata_expr(&meta)?),
"long_about" => cli.long_about_attr = Some(metadata_expr(&meta)?),
"author" => cli.author = Some(metadata_expr(&meta)?),
"license" => cli.license = Some(metadata_expr(&meta)?),
"repository" => cli.repository = Some(metadata_expr(&meta)?),
Comment thread
cursor[bot] marked this conversation as resolved.
"before_help" => cli.before_help = Some(metadata_expr(&meta)?),
"next_help_heading" => cli.next_help_heading = Some(string_value(&meta)?),
"before_long_help" => cli.before_long_help = Some(metadata_expr(&meta)?),
Expand Down Expand Up @@ -745,7 +758,7 @@ impl Cli {
path,
format!(
"unknown option `{other}` on a struct; usage::Cli takes \
`name`, `name_spec`, `bin`, `bin_spec`, `version`, `version_spec`, `usage`, `verbatim_doc_comment`, `unknown_flags`, \
`name`, `name_spec`, `bin`, `bin_spec`, `version`, `version_spec`, `author`, `license`, `repository`, `usage`, `verbatim_doc_comment`, `unknown_flags`, \
`default_subcommand`, `multicall`, `no_binary_name`, `arg_required_else_help`, `dont_delimit_trailing_values`, `args_override_self`, `subcommand_negates_reqs`, `args_conflicts_with_subcommands`, `subcommand_precedence_over_arg`, `allow_missing_positional`, \
`next_help_heading`, `subcommand_help_heading`, `term_width`, `max_term_width`, \
`subcommand_value_name`, `restart_token`, `mount` and \
Expand Down Expand Up @@ -898,6 +911,25 @@ impl Cli {
one claim about the whole emitted spec, and only the root emits one",
));
}
// Package metadata describes the emitted spec, not one command. Like
// `min_usage_version`, these values have no nested KDL location and would
// otherwise be accepted and silently dropped by `emit_args`.
if let Some(name) = [
("author", self.author.is_some()),
("license", self.license.is_some()),
("repository", self.repository.is_some()),
]
.into_iter()
.find_map(|(name, present)| present.then_some(name))
{
return Err(self.misplaced(
ident,
format!(
"`{name}` belongs on the root, where `#[derive(Cli)]` is: package \
metadata describes the whole emitted spec, not one command"
),
));
}
// A spec declares one `default_subcommand`, at the top.
if self.default_subcommand.is_some() {
return Err(self.misplaced(
Expand Down Expand Up @@ -5576,6 +5608,26 @@ mod tests {
);
}

#[test]
fn package_metadata_belongs_on_the_root() {
for attribute in ["author", "license", "repository"] {
let body = format!(
r#"
#[usage({attribute} = "value")]
struct Ex {{
#[usage(long)]
plain: bool,
}}
"#
);
let err = position_error(&body, false);
assert!(
err.contains(&format!("`{attribute}` belongs on the root")),
"unhelpful for {attribute}: {err}"
);
}
}

#[test]
fn only_a_bare_variant_declares_its_own_effect() {
// A variant that wraps a struct has somewhere to put it, and two places to say one
Expand Down
2 changes: 2 additions & 0 deletions docs/cli/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

**Version**: 5.1.0

**Repository**: https://github.com/jdx/usage

Comment thread
cursor[bot] marked this conversation as resolved.
- **Usage**: `usage [--completions <COMPLETIONS>] [--usage-spec] <SUBCOMMAND>`

## Flags
Expand Down
33 changes: 17 additions & 16 deletions docs/rust/clap-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,22 +106,23 @@ the Rust declaration, not only from generated KDL, wherever the bridge column sa

## Help, version, and generated artifacts

| clap surface | derive | argv | KDL | lib | output | bridge | Notes |
| -------------------------------------------------- | ------ | ---- | --- | --- | ------ | ------ | ---------------------------------------------------------------------------------------------- |
| short/long help and doc comments | yes | yes | yes | yes | yes | yes | First paragraph is short help; the full block is long help. |
| `help_heading` | yes | n/a | yes | yes | yes | yes | Flags and arguments are grouped and retain declaration order. |
| whole-entry `hide` | yes | yes | yes | yes | yes | yes | Hidden commands, flags, arguments, and values still parse. |
| granular hide settings | yes | yes | yes | yes | yes | yes | Default, environment, possible-value, short-help, and long-help visibility is independent. |
| `subcommand_help_heading`, `subcommand_value_name` | yes | yes | yes | yes | yes | yes | Customize the subcommand section label and the synopsis placeholder. |
| `verbatim_doc_comment` | yes | n/a | yes | yes | yes | n/a | Commands, fields, and variants preserve line breaks and indentation when requested. |
| `rename_all`, `rename_all_env` | yes | n/a | yes | yes | yes | n/a | Full clap casing vocabulary; bare `env` uses the environment casing policy. |
| `help_template`, `next_line_help`, `flatten_help` | no | n/a | no | no | no | no | No equivalent yet. |
| `term_width`, `max_term_width` | yes | yes | yes | yes | yes | no | Fixed width overrides a detected-width cap; clap exposes no bridge getters for these settings. |
| help styles and color | n/a | n/a | n/a | yes | lossy | no | Help and diagnostics use automatic ANSI styles; clap's custom style palette is not portable. |
| built-in help/version action and flag control | no | no | no | no | no | no | Custom actions and disabling or relocating built-ins are not represented. |
| `--version` / `-V`, dynamic version | yes | yes | yes | yes | yes | yes | Literal and runtime versions propagate through nested commands. |
| completion generation | yes | yes | yes | yes | lossy | yes | Bash, fish, Nushell, PowerShell, and zsh plus runtime overlays are supported; Elvish is not. |
| KDL, markdown, JSON, and manpages | yes | n/a | yes | yes | yes | yes | Direct derived KDL feeds the existing generators; broader canonicalization remains open. |
| clap surface | derive | argv | KDL | lib | output | bridge | Notes |
| -------------------------------------------------- | ------ | ---- | --- | --- | ------ | ------- | ---------------------------------------------------------------------------------------------- |
| short/long help and doc comments | yes | yes | yes | yes | yes | yes | First paragraph is short help; the full block is long help. |
| `help_heading` | yes | n/a | yes | yes | yes | yes | Flags and arguments are grouped and retain declaration order. |
| whole-entry `hide` | yes | yes | yes | yes | yes | yes | Hidden commands, flags, arguments, and values still parse. |
| granular hide settings | yes | yes | yes | yes | yes | yes | Default, environment, possible-value, short-help, and long-help visibility is independent. |
| `subcommand_help_heading`, `subcommand_value_name` | yes | yes | yes | yes | yes | yes | Customize the subcommand section label and the synopsis placeholder. |
| `verbatim_doc_comment` | yes | n/a | yes | yes | yes | n/a | Commands, fields, and variants preserve line breaks and indentation when requested. |
| `rename_all`, `rename_all_env` | yes | n/a | yes | yes | yes | n/a | Full clap casing vocabulary; bare `env` uses the environment casing policy. |
| `help_template`, `next_line_help`, `flatten_help` | no | n/a | no | no | no | no | No equivalent yet. |
| `term_width`, `max_term_width` | yes | yes | yes | yes | yes | no | Fixed width overrides a detected-width cap; clap exposes no bridge getters for these settings. |
| help styles and color | n/a | n/a | n/a | yes | lossy | no | Help and diagnostics use automatic ANSI styles; clap's custom style palette is not portable. |
| built-in help/version action and flag control | no | no | no | no | no | no | Custom actions and disabling or relocating built-ins are not represented. |
| `--version` / `-V`, dynamic version | yes | yes | yes | yes | yes | yes | Literal and runtime versions propagate through nested commands. |
| `author`, `license`, `repository` | yes | n/a | yes | yes | yes | partial | Package metadata is rendered in Markdown and manpages; clap exposes author but not license. |
| completion generation | yes | yes | yes | yes | lossy | yes | Bash, fish, Nushell, PowerShell, and zsh plus runtime overlays are supported; Elvish is not. |
| KDL, markdown, JSON, and manpages | yes | n/a | yes | yes | yes | yes | Direct derived KDL feeds the existing generators; broader canonicalization remains open. |

## Usage extensions

Expand Down
12 changes: 12 additions & 0 deletions docs/rust/migrating-from-clap.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@ struct Cli {
}
```

Package metadata is declared on the root and travels with the generated spec and references:

```rust
#[derive(usage::Cli)]
#[command(
author = "Example Maintainers",
license = "MIT OR Apache-2.0",
repository = "https://example.com/tool"
)]
struct Cli;
```

For an embedded CLI whose program name is computed, pair `name = expression` with a portable
`name_spec = "literal"`, and `bin = expression` with `bin_spec = "literal"`. The expression is
used only by process output; the literal keeps generated specs reproducible.
Expand Down
Loading