Rust CLI Tools (clap)
Build polished CLIs with clap derive, anyhow, and structured logging.
# 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
Modern Rust Rules
Idiomatic Rust: ownership, error handling with ?, traits, modules, cargo.
Rust + Axum Web Server
Axum routers, extractors, middleware, and structured error responses.
Rust + Tokio Async Patterns
Tokio fundamentals: tasks, select!, channels, cancellation, join!.
Rust Testing Patterns
Unit tests, integration tests, doctests, proptest, and CI patterns.