engl-2311-blog/blog/using-clap.md
2024-11-12 15:14:56 -06:00

5.8 KiB

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.

cargo add clap --features derive

First off, we need to get a bit of code just to start off:

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:

#[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.

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:

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.

use clap_complete::aot::{generate, Fish};

Then, we need to add a command to generate the completion:

#[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:

    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.

~> 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

Pressing tab twice after entering disk-read-benchmark displays the completions, which I can select and use like any other program's.

~> 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