All Rust templates

Rust CLI Tools (clap)

Build polished CLIs with clap derive, anyhow, and structured logging.

DevZone Tools950 copiesUpdated Mar 28, 2026Rust
# CLAUDE.md — Rust CLI Tools (clap)

## Setup

- **clap** with the `derive` feature. `cargo add clap --features derive`.
- **anyhow** for error context, **thiserror** if your CLI is also a library.
- **tracing** + **tracing-subscriber** for structured logs / verbose output.
- **indicatif** for progress bars; **dialoguer** for interactive prompts.

## Argument parsing

```rust
#[derive(Parser, Debug)]
#[command(name = "yourcli", version, about = "...", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    /// Increase verbosity (-v, -vv, -vvv)
    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
    verbose: u8,
}

#[derive(Subcommand, Debug)]
enum Commands {
    Init { #[arg(long)] force: bool },
    Deploy { target: String },
}
```

- One enum variant per command. Sub-subcommands nest as their own enum.
- `global = true` on flags that apply to every subcommand (verbosity, config path).
- Required arguments are positional; optional are `--flags`.

## Help text

- Doc comments on fields become `--help` text. Write them.
- Use `value_name = "..."` for clearer help on values: `<TARGET>` instead of `<target>`.
- Group related flags with `next_help_heading`.

## Output

- Default stdout is "the data you piped in / out of". Logs and progress go to stderr.
- Use **owo-colors** for color output, gated on `is-terminal` so piped output stays plain.
- Honor `NO_COLOR` env var. Most color crates do this automatically.

## Errors

- Return `anyhow::Result<()>` from `main`. `anyhow`'s `Display` includes the chain.
- Use `.context("doing X")` to add layered context to errors.
- For known failure modes (file not found, conflict), exit with a specific code via `std::process::exit(2)` after printing.

## Configuration order

- Flag → env var → config file → default.
- For env vars, clap's `env = "VAR"` attribute. For config files, **figment** or **config** crates parse YAML/TOML/JSON.

## Logging

- Map `-v` count to log level: 0 = warn, 1 = info, 2 = debug, 3+ = trace.
- `tracing-subscriber::fmt().with_env_filter(...)` reads `RUST_LOG` for fine-grained filters.
- Don't `println!` for log output. Use `tracing::info!` and friends.

## Testing

- Library half is tested normally. Binary half via **assert_cmd**:
  ```rust
  Command::cargo_bin("yourcli")?
      .args(["init", "--force"])
      .assert()
      .success()
      .stdout(predicate::str::contains("ok"));
  ```
- Snapshot tests for help output with **insta**.

## Distribution

- `cargo install yourcli` from crates.io.
- For binaries, **cargo-dist** generates GitHub Actions to build and publish per-target.
- Static-link with **musl** target on Linux for portable single binaries.

## Don't

- Don't print plain `{:?}` of an error for users. Format the chain.
- Don't write CLI logic into `main`. `main` does setup and calls into a `run(args) -> Result<()>`.
- Don't silently consume `--help` or `--version`. Let clap handle them.
- Don't ship interactive prompts with no `--yes` / `--non-interactive` opt-out.

Other Rust templates