Skip to content

Quickstart

Draft

This page is a draft. Some of what it documents is still in open pull requests, and details may change before release.

One small CLI, end to end: declare it, run it, complete it, and generate its docs — all from one declaration. Every output on this page is the real output of the code shown.

A new project

bash
cargo new greet
toml
[dependencies]
usage = { package = "usage-rs", version = "6", features = ["completions"] }

The declaration

rust
use usage::{Args, Cli, Run, Subcommands};

/// Greets people, politely
#[derive(Cli)]
#[usage(bin = "greet", version = "0.1.0", completion)]
struct Greet {
    /// Say it louder
    #[usage(short = 'l', long, count, global)]
    loud: u8,

    #[usage(subcommand)]
    command: Commands,
}

#[derive(Subcommands)]
#[usage(run)]
enum Commands {
    Hello(Hello),
    Completion(Completion),
}

/// Greet someone
#[derive(Args)]
struct Hello {
    /// Who to greet
    #[usage(env = "GREET_NAME", default = "world")]
    name: String,
}

impl Run for Hello {
    type Output = ();
    fn run(self) {
        println!("hello, {}", self.name);
    }
}

/// Print a completion script
#[derive(Args)]
struct Completion {
    /// Which shell to generate for
    #[usage(long, choices("bash", "zsh", "fish"))]
    shell: String,
}

impl Run for Completion {
    type Output = ();
    fn run(self) {
        let shell = match self.shell.as_str() {
            "bash" => usage::complete::Shell::Bash,
            "zsh" => usage::complete::Shell::Zsh,
            _ => usage::complete::Shell::Fish,
        };
        print!("{}", Greet::completion_script(shell));
    }
}

fn main() {
    Greet::parse().command.run()
}

That is the whole program. The struct is the grammar, the doc comments are the help text, the Run impls are the commands, and main is one line: parse() handles help, version, errors, and completion requests, and #[usage(run)] generated the match that routes the selected command to its impl.

Run it

console
$ greet hello
hello, world

$ GREET_NAME=Jeff greet hello       # env fallback, declared on the field
hello, Jeff

$ greet -l hello usage              # a global flag, and a positional
hello, usage

Help was never declared, and never drifts from the declaration:

console
$ greet --help
greet 0.1.0
Greets people, politely

Usage: greet [-l --loud…] <SUBCOMMAND>

Commands:
  completion <--shell <SHELL>>
    Print a completion script

  hello [NAME]
    Greet someone

  help
    Print this message or the help of the given subcommand(s)

Flags:
  -l, --loud…    Say it louder
  -h, --help     Print help
  -V, --version  Print version

Errors are clap-shaped, exit status included:

console
$ greet helo
error: unrecognized subcommand 'helo'

  tip: a similar subcommand exists: 'hello'

Usage: greet [-l --loud…] <SUBCOMMAND>

For more information, try '--help'.

$ echo $?
2

Completions

The completion command above already works — #[usage(completion)] generated the script methods, and parse() answers the runtime protocol the script calls back with:

console
$ greet completion --shell fish | head -3
# @generated by usage-argv for `greet __complete_word__ --shell fish`
# Do not edit: regenerate it. Needs no other program, and no cached spec —
# the binary answers from the tables it was compiled with.

Tab-completing greet now offers hello and completion with their descriptions; --shell offers bash, zsh, fish. install_completion can put the script where the shell looks for it — see Completions.

Docs and manpages

Every binary answers __usage_spec__ with its spec, so the rest of the toolkit needs no wiring:

bash
greet __usage_spec__ > greet.usage.kdl
usage g markdown -f greet.usage.kdl --out-dir docs   # markdown docs
usage g manpage  -f greet.usage.kdl > greet.1        # man page

The emitted spec is the same declaration, portably:

kdl
name greet
bin greet
version "0.1.0"
about "Greets people, politely"
subcommand_required #true
flag "-l --loud" help="Say it louder" global=#true count=#true var=#true
cmd hello help="Greet someone" {
    arg "[NAME]" help="Who to greet" env=GREET_NAME default=world
}

A test

toml
[dev-dependencies]
usage = { package = "usage-rs", version = "6", features = ["test", "completions"] }
rust
#[test]
fn hello_takes_a_name() {
    let words = usage::test::argv(["hello", "Jeff"]);
    let cli = usage::test::parse(Greet::spec(), &words.words(), Greet::parse_from).unwrap();
    assert!(matches!(cli.command, Commands::Hello(h) if h.name == "Jeff"));
}

No process spawned, no output captured — the same tables parse() runs on. See Testing for help-page snapshots and completion assertions.

Where next

MIT LicenseCopyright © 2026jdx.dev