Clap, with the derive
feature, allows us to specify the arguments we want to accept in our CLI by defining regular Rust structs and using derive macros.
use clap::Parser;
#[derive(Parser, Debug)]
struct Args {
#[clap(short, long, default_value = "post")]
layout: String,
#[clap(short, long = "tag")]
tags: Vec<String>,
#[clap(short = 'T', long, default_value = "A Post")]
title: String,
#[clap(short, long, default_value = "draft")]
status: String,
#[clap(short, long, default_value = "content")]
output_dir: String,
}
fn main() {
let args = Args::parse();
dbg!(args);
}
This completely defines our CLI’s interface. Running the CLI right now provides us with the Args
struct, which is an arbitrary name, full of default values.
We know this because the dbg!
macro shows us the args value when we run the program.
❯ cargo run
Compiling bitflags v2.3.3
Compiling utf8parse v0.2.1
Compiling anstyle v1.0.1
Compiling anstyle-query v1.0.0
Compiling colorchoice v1.0.0
Compiling libc v0.2.147
Compiling strsim v0.10.0
Compiling clap_lex v0.5.0
Compiling once_cell v1.18.0
Compiling anstyle-parse v0.2.1
Compiling errno v0.3.1
Compiling rustix v0.38.4
Compiling is-terminal v0.4.9
Compiling anstream v0.3.2
Compiling clap_builder v4.3.19
Compiling clap v4.3.19
Compiling scaffold v0.1.0 (/rust-adventure/scaffold)
Finished dev [unoptimized + debuginfo] target(s) in 2.55s
Running `target/debug/scaffold`
[src/main.rs:22] args = Args {
layout: "post",
tags: [],
title: "A Post",
status: "draft",
output_dir: "content",
}
There are three major pieces to this program.
- Our
Args
struct - The clap derive macros
- Our main function
The Args struct
structs are one way for us to define our own data types in Rust. They’re like objects with string keys and typed values.
Without the Clap dressings, we can see an Args
struct with five fields. Each field is either a String
or a Vec<String>
.
struct Args {
layout: String,
tags: Vec<String>,
title: String,
status: String,
output_dir: String,
}
This is exactly what we’ll get when we parse
the arguments later: a value of type Args
with each of the fields filled out.
clap’s derive macros
derive macros generate code. They’re often used to generate what would otherwise be mundane and generic code, such as how we’re deriving an implementation for Debug
here.
The Debug
derive macro generates code that implements code that satisfies the Debug
trait. This code is unremarkable and basically the same for any struct, so we don’t want to keep writing it over and over.
Using the dbg!
macro requires that the value we’re printing implements the Debug
trait, so deriving Debug
allows us to generate that code.
The Debug
derive macro is provided by Rust itself, but crates can also provide their own.
clap::Parser
The clap crate provides a derive macro we can use to generate the code that would otherwise parse the arguments passed to our CLI. It does this by using the Parser
derive macro and the additional information we give using the clap
helper attribute on each field.
We place the Parser
derive macro on our Args
struct, which the macro can then read and process.
The Parser
derive macro will generate some code that implements the Parser
trait for our Args
struct and thus associates the relevant functions on the Parser
trait with our Args
struct.
This trait requires implementing a parse
function, so in our main function we can run Args::parse
to parse the arguments since we just generated the code powering that function.
clap()
The clap helper allows us to define a flag and a default value for each field.
In our case, we define a short flag, a long flag, and sometimes a default value on each field.
Short flags are single characters. A short flag s
would show up as -s
when passing arguments to our CLI.
long flags are full names. A long flag named scores
would show up as --scores
in our CLI.
fields can have short names and long names at the same time, or they can be a positional argument.
The main function
We’ve generated the code that powers the parse
function, so we can use Args::parse
in our main function.
fn main() {
let args = Args::parse();
dbg!(args);
}
This function returns one of our Args
structs populated with all the values clap grabbed from the arguments.
We’ve derived Debug
on Args
, so we can print out the values that got populated with dbg!
.
Running the CLI
We can use cargo run
as many times as we want. Notice how the Vec flag for tags behaves differently because it is a Vec. Clap’s handling of Vec
s means that we get to use the flag multiple times.
We can also specify the title of the post, an arbitrary layout, and whether the post should be published or not.
❯ cargo run -- -t bevy -t rust -t shaders -T "New shaders in Bevy"
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/scaffold -t bevy -t rust -t shaders -T 'New shaders in Bevy'`
[src/main.rs:22] args = Args {
layout: "post",
tags: [
"bevy",
"rust",
"shaders",
],
title: "New shaders in Bevy",
status: "draft",
output_dir: "content",
}
If this is your first time, it may be easier to play with flags using the actual binary instead of using the --
after cargo run.
The binary is located in target/debug/scaffold
because cargo run
also transparently runs cargo build
for us.
❯ ./target/debug/scaffold
[src/main.rs:22] args = Args {
layout: "post",
tags: [],
title: "A Post",
status: "draft",
output_dir: "content",
}
❯ ./target/debug/scaffold -t bevy -t rust -t shaders -T "New shaders in Bevy"
[src/main.rs:22] args = Args {
layout: "post",
tags: [
"bevy",
"rust",
"shaders",
],
title: "New shaders in Bevy",
status: "draft",
output_dir: "content",
}
With the arguments parsing, let’s move on to defining the help text.