engl-2311-blog/blog/using-clap.md

171 lines
5.8 KiB
Markdown
Raw Normal View History

2024-11-12 15:14:56 -06:00
# Using `clap`
Clap stands for Command Line Argument Parser, and put simply, it's a great library for making command-line stuff with Rust. Even Cargo, Rust's package manager, depends on it [4], and it's been downloaded over 300 million times [5].
Rather than going over everything clap can do, I'll go over how I've used it in my `disk-read-benchmark` program I'll be using in my next blog post.
## Basics
First off, we need to install `clap`; make sure to enable its `derive` feature as that's what we'll be using.
```sh
cargo add clap --features derive
```
First off, we need to get a bit of code just to start off:
```rust
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
```
This has the built-in "version" and "about" options, with the long "about" option disabled.
Next, we need to list all out commands we'll have:
```rust
#[derive(Subcommand)]
pub enum Commands {
///Run this thing
Run,
///Delete the stuff that thing does
Delete,
}
```
The documentation comments (`///`) should *not* have a space after the slashes, as otherwise the program will have an extra space where it shouldn't.
Finally, we create the `main()` function. First it parses everything, then checks what command was run and runs the relevant code.
```rust
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Run {
run();
}
Commands::Delete {
delete();
}
}
}
```
That's all you need to know to use `clap` at a very basic level; for more details, check out the docs [1]. But, you probably don't want to have to type in the entire command automatically, autocomplete would be nice. So I'll also go over how to use `clap_complete` as well.
## `clap_complete`
Searching through the documentation [2], you'll notice that the docs don't cover how to use it with clap's derive at all. Instead, after some Googling, I found an example script in *clap*'s repository [3], which I then adapted and played around with a bit until I got it figured out.
Anyways, again, we need to install `clap_complete` first:
```sh
cargo add clap_complete
```
Then, add the relevant imports. We'll just being doing it for the fish shell since that's what I use, so we'll only import `Fish`; Bash, Zsh, PowerShell, and Elvish are also supported.
```rust
use clap_complete::aot::{generate, Fish};
```
Then, we need to add a command to generate the completion:
```rust
#[derive(Subcommand)]
pub enum Commands {
///Run this thing
Run,
///Delete the stuff that thing does
Delete,
///Generate fish completions
FishCompletions,
}
```
Next, we actually generate the completion, adding it like it's another command:
```rust
match cli.command {
Commands::Run {
run();
}
Commands::Delete {
delete();
}
Commands::GenerateFishCompletions => {
generate(
Fish,
&mut Cli::command(),
"example-program",
&mut stdout(),
);
}
}
```
To explain the options for `generate()`:
- `Fish`: The shell we're using.
- `&mut Cli::command()`: I don't actually know what this does, but understanding ths library fully this is beyond my pay grade, especially given the somewhat lacking docs.
- `"example-program"`: The name of our program
- `&mut stdout()`: `stdout`, so that it can print the completions. Why does it do it this way? I don't know, it doesn't make sense to me. Why doesn't it just return it as a String? I don't know. But it works, I suppose.
As an example of all this, here's my `disk-read-benchmark` program, running using all this. The commands have formatting I can't do, so it looks even better than I can even show here.
```txt
~> disk-read-benchmark
Usage: disk-read-benchmark <COMMAND>
Commands:
generate-bash-completions Generate bash completions
generate-zsh-completions Generate zsh completions
generate-fish-completions Generate fish completions
grab-data Grabs the datasets used for benchmarking
benchmark Runs the benchmark
prep-dirs Prepares the directories so other programs can prepare their datasets
run Runs it all
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
~> disk-read-benchmark generate-fish-completions | source
~> disk-read-benchmark benchmark --help
Runs the benchmark
Usage: disk-read-benchmark benchmark
Options:
-h, --help Print help
```
To better see how great it looks, here's a screenshot:
![The same output, but with very nice formatting - underlining and bolding for headers and the tables](/assets/using-clap/1.png)
Pressing tab twice after entering `disk-read-benchmark` displays the completions, which I can select and use like any other program's.
```txt
~> disk-read-benchmark
benchmark (Runs the benchmark) grab-data (Grabs the datasets used for benchmarking)
generate-bash-completions (Generate bash completions) help (Print this message or the help of the given subcommand(s))
generate-fish-completions (Generate fish completions) prep-dirs (Prepares the directories so other programs can prepare their datasets)
generate-zsh-completions (Generate zsh completions) run (Runs it all)
```
## Sources
1. <https://docs.rs/clap/latest/clap/>
2. <https://docs.rs/clap_complete/latest/clap_complete/>
3. <https://github.com/clap-rs/clap/blob/master/clap_complete/examples/completion-derive.rs>
4. <https://github.com/rust-lang/cargo/blob/master/Cargo.toml>
5. <https://crates.io/crates/clap>