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
15 changes: 2 additions & 13 deletions Cargo.lock

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

18 changes: 16 additions & 2 deletions argv/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ pub fn usage_line(path: &[&str], meta: &CommandMeta<'_>) -> String {
out
}

/// Write the synopsis for a page, preferring the root's explicit alternatives.
///
/// An explicit synopsis belongs to the program rather than every command below it. Subcommand
/// pages still derive their own invocation from the route and command metadata.
fn usage_section(out: &mut String, spec: &Spec<'_>, path: &[&str], meta: &CommandMeta<'_>) {
if path.len() <= 1 {
if let Some(usage) = spec.usage.filter(|usage| !usage.trim().is_empty()) {
let _ = writeln!(out, "{}", usage.trim());
return;
}
}
let _ = writeln!(out, "Usage: {}", usage_line(path, meta));
}

/// How one flag appears in the usage line: `-f --force`, plus its value if it takes one.
fn flag_usage(meta: &FlagMeta<'_>) -> String {
flag_usage_masked(meta, &Shown::all(meta))
Expand Down Expand Up @@ -327,7 +341,7 @@ pub fn short_help(spec: &Spec<'_>, path: &[&str], chain: &[&CommandMeta<'_>]) ->
if let Some(about) = about {
let _ = writeln!(out, "{about}\n");
}
let _ = writeln!(out, "Usage: {}", usage_line(path, meta));
usage_section(&mut out, spec, path, meta);

// The path without the binary, which is what a listed subcommand shows: usage-lib prints
// `tool-alias get <TOOL>` under `mise tool-alias`, the whole path from the root rather
Expand Down Expand Up @@ -668,7 +682,7 @@ pub fn long_help(spec: &Spec<'_>, path: &[&str], chain: &[&CommandMeta<'_>]) ->
if let Some(about) = about {
let _ = writeln!(out, "{about}\n");
}
let _ = writeln!(out, "Usage: {}", usage_line(path, meta));
usage_section(&mut out, spec, path, meta);

long_commands_section(&mut out, &path[1.min(path.len())..], meta);

Expand Down
7 changes: 7 additions & 0 deletions argv/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ pub struct Spec<'a> {
pub min_usage_version: Option<&'a str>,
pub about: Option<&'a str>,
pub long_about: Option<&'a str>,
/// An exact usage synopsis, including the `Usage:` prefix, when the generated
/// shape needs alternatives that cannot be inferred from one command grammar.
pub usage: Option<&'a str>,
/// Which command the root falls back to when a word matches no subcommand.
/// mise uses this so `mise foo` completes as `mise run foo`.
pub default_subcommand: Option<&'a str>,
Expand All @@ -293,6 +296,7 @@ impl Spec<'_> {
min_usage_version: None,
about: None,
long_about: None,
usage: None,
default_subcommand: None,
root: &CommandMeta::EMPTY,
};
Expand Down Expand Up @@ -672,6 +676,9 @@ impl Spec<'_> {
if let Some(long_about) = self.long_about.or(self.root.long_about) {
prop(out, "long_about", long_about)?;
}
if let Some(usage) = self.usage {
prop(out, "usage", usage)?;
}
// Written only when it is not the default, so an ordinary spec stays quiet
// about it.
if self.root.cmd.unknown_flags == Some(UnknownFlags::Error) {
Expand Down
7 changes: 4 additions & 3 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ name = "usage_cli"
path = "src/lib.rs"

[dependencies]
clap = { version = "4", features = ["derive", "string", "env"] }
clap_usage = { workspace = true }
env_logger = "0.11"
indexmap = "2"
itertools = "0.15"
Expand All @@ -43,6 +41,10 @@ serde_with = "3"
tera = "2"
thiserror = "2"
tokio = { version = "1", features = ["rt", "macros", "io-std"] }
# The CLI is the facade's first adopter: `usage` parses its own command line with
# the parser it ships. `diagnostics` includes spec emission for `--usage-spec` and
# the errors a person needs when a command line does not parse.
usage-rs = { workspace = true, features = ["diagnostics"] }
usage-lib = { workspace = true, features = ["clap", "docs", "unstable_choices_env"] }
xx = "2"

Expand All @@ -51,7 +53,6 @@ exec = "0.3"

[dev-dependencies]
assert_cmd = { version = "2", features = ["color-auto"] }
clap-sort = "1"
ctor = "1"
insta = "1"
predicates = "3"
Expand Down
16 changes: 10 additions & 6 deletions cli/assets/fig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ const completionSpec: Fig.Spec = {
},
{
name: ["manpage", "man"],
description: "Generate a manpage from a usage spec",
options: [
{
name: ["-f", "--file"],
Expand Down Expand Up @@ -715,18 +716,21 @@ const completionSpec: Fig.Spec = {
},
],
options: [
{
name: "--completions",
description:
"Outputs completions for the specified shell for completing the `usage` CLI itself",
isRepeatable: false,
args: {
name: "completions",
},
},
{
name: "--usage-spec",
description: "Outputs a `usage.kdl` spec for this CLI itself",
isRepeatable: false,
},
],
args: {
name: "completions",
description:
"Outputs completions for the specified shell for completing the `usage` CLI itself",
isOptional: true,
},
};

export default completionSpec;
63 changes: 49 additions & 14 deletions cli/assets/usage.1
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
.TH USAGE-CLI 1
.TH USAGE 1
.SH NAME
usage\-cli \- CLI for working with usage\-based CLIs
usage \- CLI for working with usage\-based CLIs
.SH SYNOPSIS
\fBusage\fR [OPTIONS] [<COMPLETIONS>] [COMMAND]
\fBusage\fR <COMMAND>
\fBusage\fR \-\-completions <COMPLETIONS>
\fBusage\fR \-\-usage\-spec
.SH DESCRIPTION
CLI for working with usage\-based CLIs
.PP
.SH OPTIONS
.TP
\fB\-\-completions\fR \fI<COMPLETIONS>\fR
Outputs completions for the specified shell for completing the `usage` CLI itself
.TP
\fB\-\-usage\-spec\fR
Outputs a `usage.kdl` spec for this CLI itself
.SH ARGUMENTS
.TP
\fB<COMPLETIONS>\fR
Outputs completions for the specified shell for completing the `usage` CLI itself
.SH COMMANDS
.TP
\fBbash\fR
Expand Down Expand Up @@ -65,6 +66,7 @@ Outputs a usage spec in json format
Generate a JSON Schema for a CLI's config file from its usage spec
.TP
\fBgenerate manpage\fR
Generate a manpage from a usage spec
.RS
\fIAliases: \fRman
.RE
Expand Down Expand Up @@ -118,10 +120,14 @@ Show help
.TP
\fB<ARGS>\fR
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 "USAGE COMPLETE-WORD"
Generate shell completion candidates for a partial command line

This is used internally by shell completion scripts to provide intelligent completions for commands, flags, and arguments.
This is used internally by shell completion scripts to provide
intelligent completions for commands, flags, and arguments.
.PP
\fBUsage:\fR usage complete\-word [OPTIONS] [<WORDS>] ...
.PP
Expand Down Expand Up @@ -193,6 +199,9 @@ Show help
.TP
\fB<ARGS>\fR
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 "USAGE GENERATE COMPLETION"
Generate shell completion scripts for bash, fish, nu, powershell, or zsh
.PP
Expand All @@ -219,9 +228,14 @@ You may need to set this if you have a different bin named "usage"
.RS
\fIDefault: \fRusage
.RE
.RS
\fIEnvironment: \fR\fBJDX_USAGE_BIN\fR
.RE
.TP
\fB\-\-usage\-cmd\fR \fI<USAGE_CMD>\fR
A command which generates a usage spec e.g.: `mycli \-\-usage` or `mycli completion usage` Defaults to "$bin \-\-usage"
A command which generates a usage spec
e.g.: `mycli \-\-usage` or `mycli completion usage`
Defaults to "$bin \-\-usage"
\fBArguments:\fR
.PP
.TP
Expand All @@ -233,7 +247,9 @@ The CLI which we're generating completions for
.SH "USAGE GENERATE COMPLETION-INIT"
Generate a shell init script that auto\-completes any usage shebang script on $PATH

Source the output once from your shell rc (e.g. ~/.bashrc) to enable tab\-completion for any executable whose first line is a `usage` shebang — no per\-script `usage g completion` step required.
Source the output once from your shell rc (e.g. ~/.bashrc) to enable
tab\-completion for any executable whose first line is a `usage` shebang —
no per\-script `usage g completion` step required.
.PP
\fBUsage:\fR usage generate completion\-init [OPTIONS] <SHELL>
.PP
Expand All @@ -247,6 +263,9 @@ You may need to set this if you have a different bin named "usage"
.RS
\fIDefault: \fRusage
.RE
.RS
\fIEnvironment: \fR\fBJDX_USAGE_BIN\fR
.RE
\fBArguments:\fR
.PP
.TP
Expand All @@ -271,9 +290,11 @@ Raw string spec input
.SH "USAGE GENERATE GO"
Generate Go parse tables from a usage spec

The tables are read by github.com/jdx/usage/go/argv. Go has no macros, so what a Rust CLI gets from a derive at compile time, a Go CLI gets from this at build time — typically from a `go:generate` line:
The tables are read by github.com/jdx/usage/go/argv. Go has no macros, so what
a Rust CLI gets from a derive at compile time, a Go CLI gets from this at build
time — typically from a `go:generate` line:

//go:generate usage generate go \-f mycli.usage.kdl \-o tables.go
//go:generate usage generate go \-f mycli.usage.kdl \-o tables.go
.PP
\fBUsage:\fR usage generate go [OPTIONS]
.PP
Expand Down Expand Up @@ -327,6 +348,8 @@ The schema's title, shown by editors
\fB\-\-url\fR \fI<URL>\fR
Where the schema is published, for its `$id`
.SH "USAGE GENERATE MANPAGE"
Generate a manpage from a usage spec
.PP
\fBUsage:\fR usage generate manpage [OPTIONS]
.PP
\fBOptions:\fR
Expand All @@ -341,7 +364,11 @@ Output file path, or "\-" for stdout (default)
\fB\-s, \-\-section\fR \fI<SECTION>\fR
Manual section number (default: 1)

Common sections: \- 1: User commands \- 5: File formats \- 7: Miscellaneous \- 8: System administration commands
Common sections:
\- 1: User commands
\- 5: File formats
\- 7: Miscellaneous
\- 8: System administration commands
.RS
\fIDefault: \fR1
.RE
Expand Down Expand Up @@ -415,7 +442,9 @@ Treat warnings as errors
\fB\-\-sorted\fR
Also check that subcommands and flags are declared in sorted order

Off by default: declaration order is a house convention rather than a correctness question, so a spec that keeps a different order is not wrong. Pair it with \-\-warnings\-as\-errors to hold the order in CI.
Off by default: declaration order is a house convention rather than a
correctness question, so a spec that keeps a different order is not wrong.
Pair it with \-\-warnings\-as\-errors to hold the order in CI.
\fBArguments:\fR
.PP
.TP
Expand Down Expand Up @@ -460,6 +489,9 @@ Show help
.TP
\fB<ARGS>\fR
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 "USAGE ZSH"
Execute a shell script with the specified shell

Expand All @@ -483,3 +515,6 @@ Show help
.TP
\fB<ARGS>\fR
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.
16 changes: 10 additions & 6 deletions cli/src/cli/complete_word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use clap::Args;
use itertools::Itertools;
use miette::IntoDiagnostic;
use std::sync::LazyLock;
use usage_rs::Args;
use xx::regex;

use usage::parse::{ParseOutput, ParseValue};
Expand All @@ -23,24 +23,28 @@ use crate::cli::generate;
/// This is used internally by shell completion scripts to provide
/// intelligent completions for commands, flags, and arguments.
#[derive(Debug, Args)]
#[clap(visible_alias = "cw")]
#[usage(alias = "cw", effect = "read")]
pub struct CompleteWord {
/// User's input from the command line
words: Vec<String>,

/// Usage spec file or script with usage shebang, use "-" to read from stdin
#[clap(short, long)]
#[usage(short, long)]
file: Option<PathBuf>,

/// Raw string spec input
#[clap(short, long, required_unless_present = "file", overrides_with = "file")]
#[usage(short, long, required_unless = "--file", overrides = "--file")]
spec: Option<String>,

/// Current word index
#[clap(long, allow_hyphen_values = true)]
#[usage(long)]
cword: Option<usize>,

#[clap(long, default_value = "bash", value_parser = ["bash", "fish", "nu", "powershell", "zsh"])]
#[usage(
long,
default = "bash",
choices("bash", "fish", "nu", "powershell", "zsh")
)]
shell: String,
}

Expand Down
Loading
Loading