Skip to content

Commit 39a7c61

Browse files
committed
update original
1 parent 0f290e8 commit 39a7c61

7 files changed

Lines changed: 245 additions & 1 deletion

File tree

rust-cookbook/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ bitflags = "1.3.2"
5757
byteorder = "1.0"
5858
cc = "1.0"
5959
chrono = "0.4"
60-
clap = "4.5"
60+
clap = { version = "4.5", features = ["derive"] }
6161
crossbeam = "0.8"
6262
crossbeam-channel = "0.5"
6363
csv = "1.0"

rust-cookbook/ci/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ StripPrefixError
350350
strs
351351
struct
352352
structs
353+
subcommand
353354
subcommands
354355
subdirectories
355356
subfolders

rust-cookbook/src/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
- [Structured Concurrency](asynchronous/join.md)
1616
- [Command Line](cli.md)
1717
- [Argument Parsing](cli/arguments.md)
18+
- [Clap Derive](cli/clap-derive.md)
19+
- [Clap Subcommands](cli/clap-subcommand.md)
20+
- [Argument Validation](cli/clap-validation.md)
1821
- [ANSI Terminal](cli/ansi_terminal.md)
1922
- [Environment Variables](cli/env.md)
2023
- [Compression](compression.md)

rust-cookbook/src/cli.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
|--------|--------|------------|
55
| [Parse command line arguments][ex-argument-basic] | [![std-badge]][std] | [![cat-command-line-badge]][cat-command-line] |
66
| [Clap Basic Argument Parsing][ex-argument-clap-basic] | [![clap-badge]][clap] | [![cat-command-line-badge]][cat-command-line] |
7+
| [Clap Derive][ex-argument-clap-derive] | [![clap-badge]][clap] | [![cat-command-line-badge]][cat-command-line] |
8+
| [Clap Subcommands][ex-clap-subcommands] | [![clap-badge]][clap] | [![cat-command-line-badge]][cat-command-line] |
9+
| [Clap Arguments Validation][ex-clap-validation] | [![clap-badge]][clap] | [![cat-command-line-badge]][cat-command-line] |
710
| [ANSI Terminal][ex-ansi_term-basic] | [![ansi_term-badge]][ansi_term]| [![cat-command-line-badge]][cat-command-line] |
811
| [Environment Variables][ex-env] | [![std-badge]][std]| [![cat-command-line-badge]][cat-command-line] |
912
| [Load Config][ex-load-config] | [![std-badge]][std]| [![cat-command-line-badge]][cat-command-line] |
1013

1114
[ex-ansi_term-basic]: cli/ansi_terminal.html#ansi-terminal
1215
[ex-argument-basic]: cli/arguments.html#basic-argument-parsing
1316
[ex-argument-clap-basic]: cli/arguments.html#clap-basic
17+
[ex-argument-clap-derive]: cli/clap-derive.html
18+
[ex-clap-subcommands]: cli/clap-subcommand.html
19+
[ex-clap-validation]: cli/clap-validation.html
1420
[ex-env]: cli/env.html
1521
[ex-load-config]: cli/env.html#loading-config-from-the-environment
1622

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Clap Derive Parser Macro
2+
3+
Clap's [`Parser`] derive macro is the most common way to define a command line interface in Rust.
4+
Instead of manually parsing `std::env::args()`, you describe the shape of your arguments as a
5+
struct, and Clap generates all the parsing logic for you. This recipe defines the arguments for a
6+
backend server, including a required server URL and several optional settings.
7+
8+
Whether an argument is required or optional depends on its type. Wrapping a field in `Option<T>`
9+
makes it optional, while using `T` directly makes it required. In this recipe, `server_address` is
10+
the only required argument, while `log_level`, `server_port`, and `snapshot_frequency` may all be
11+
omitted when running the program.
12+
13+
Since command line input always arrives as plain text, custom types like `LogLevel` need a way to be
14+
matched against text. Deriving [`ValueEnum`] tells Clap which strings map to which variants, by
15+
default the lowercased variant names, so `--log-level warn` selects `LogLevel::Warn`. Any other
16+
value is rejected before the program runs, with an error that lists the valid choices. Each field's
17+
`///` doc comment becomes part of the generated `--help` output.
18+
19+
> This recipe requires the [`derive`] feature flag to be enabled in `Cargo.toml`.
20+
21+
```rust,edition2024,no_run
22+
use clap::{Parser, ValueEnum};
23+
use std::net::IpAddr;
24+
25+
#[derive(ValueEnum, Clone, Debug)]
26+
enum LogLevel {
27+
Debug,
28+
Error,
29+
Warn,
30+
Trace,
31+
Info,
32+
}
33+
34+
#[derive(Parser, Clone, Debug)]
35+
struct CliArgs {
36+
/// Logging threshold
37+
#[arg(short = 'l', long = "log-level")]
38+
log_level: Option<LogLevel>,
39+
40+
/// The Backend Server Address
41+
#[arg(short = 'a', long = "server-address")]
42+
server_address: IpAddr,
43+
44+
/// The Backend Server Port
45+
#[arg(short = 'p', long = "server-port")]
46+
server_port: Option<u16>,
47+
48+
/// Interval between database snapshots in seconds
49+
#[arg(short = 's', long = "snapshot-freq")]
50+
snapshot_frequency: Option<usize>,
51+
}
52+
53+
fn main() {
54+
let args = CliArgs::parse();
55+
dbg!(args);
56+
}
57+
```
58+
[`Parser`]: https://docs.rs/clap/*/clap/trait.Parser.html
59+
[`ValueEnum`]: https://docs.rs/clap/*/clap/trait.ValueEnum.html
60+
[`derive`]: https://docs.rs/clap/*/clap/_features/index.html
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Clap Subcommands
2+
3+
Many command line tools split their functionality into subcommands. Familiar tool commands include
4+
`git commit` and `cargo build`, where the first word selects a specific action with its own set of
5+
arguments. Clap supports this pattern through the [`Subcommand`] trait, shown here in a small
6+
note-taking application with four subcommands: `new`, `list`, `delete`, and `update`.
7+
8+
Each variant of the `Commands` enum represents one subcommand, and the fields on that variant become
9+
the arguments accepted by it. By default, these fields are parsed as positional arguments. Adding
10+
`#[arg(short, long)]` turns a field into a named flag or option instead, such as `--title` or `-t`.
11+
Doc comments written above each variant and field are also used by Clap to automatically generate
12+
the help text shown when running the program with `--help`.
13+
14+
Some arguments are not specific to a single subcommand. The `color` field on NotesArgs is marked
15+
with ``#[arg(global = true)]``, which makes it available before or after any subcommand, such as
16+
`notes --color new` or `notes new --color`. This is a convenient way to define flags or options that
17+
should apply across the entire application rather than to one action.
18+
19+
> This recipe requires the [`derive`] feature flag to be enabled in `Cargo.toml`.
20+
21+
```rust,edition2024,no_run
22+
use clap::{Parser, Subcommand};
23+
24+
#[derive(Parser)]
25+
struct NotesArgs {
26+
/// Enable syntax highlighting
27+
#[arg(global = true, short = 'c', long = "color")]
28+
color: bool,
29+
30+
#[command(subcommand)]
31+
command: Commands,
32+
}
33+
34+
#[derive(Subcommand)]
35+
enum Commands {
36+
/// Create a new note
37+
New {
38+
#[arg(short = 't', long = "title", help = "Title of the new note")]
39+
title: String,
40+
#[arg(short = 'b', long = "body", help = "Description of the note")]
41+
body: Option<String>,
42+
},
43+
44+
/// List all available notes
45+
List,
46+
47+
/// Delete a note by its index. 1-based
48+
Delete {
49+
/// Index of the note
50+
index: u64,
51+
},
52+
53+
/// Update an existing note
54+
Update {
55+
/// Index of the note
56+
index: u64,
57+
58+
#[arg(short = 't', long = "title", help = "Updated title of the note")]
59+
title: Option<String>,
60+
61+
#[arg(short = 'b', long = "body", help = "Updated description of the note")]
62+
body: Option<String>,
63+
},
64+
}
65+
66+
fn main() {
67+
let args = NotesArgs::parse();
68+
if args.color {
69+
println!("Enabled syntax highlighting");
70+
}
71+
72+
match args.command {
73+
Commands::New { title, body } => {
74+
// Prints:
75+
// TITLE: ...
76+
// BODY: ...
77+
println!(
78+
"Creating new note!\nTITLE: {}\n{}",
79+
title,
80+
if let Some(body) = body {
81+
format!("BODY: {}", body)
82+
} else {
83+
"".into()
84+
}
85+
);
86+
}
87+
Commands::List => println!("Listing available tasks"),
88+
Commands::Delete { index } => println!("Deleting note {index}"),
89+
Commands::Update { index, title, body } => {
90+
println!("Updating note {index}");
91+
if let Some(title) = title {
92+
println!("NEW TITLE: {title}");
93+
}
94+
if let Some(body) = body {
95+
println!("NEW BODY: {body}");
96+
}
97+
}
98+
}
99+
}
100+
```
101+
102+
[`derive`]: https://docs.rs/clap/*/clap/_features/index.html
103+
[`Subcommand`]: https://docs.rs/clap/*/clap/trait.Subcommand.html
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Clap Argument Validation
2+
3+
Not every argument can be trusted simply because it parses into the right type. A string might
4+
convert fine into a [`PathBuf`] or a number, while still being meaningless in context, such as a
5+
percentage over 100 or a path that does not point to an existing file. Clap addresses this through
6+
the [`value_parser`] attribute, which lets you attach custom validation logic to an argument beyond
7+
basic type conversion. This recipe uses a disk-cleanup tool to demonstrate three common validation
8+
patterns: numeric ranges, file existence checks, and custom string formats.
9+
10+
For numeric ranges, Clap provides a built-in solution. The `threshold` field uses
11+
`value_parser!(u8).range(0..=100)`, which rejects any value outside that range before the program
12+
even runs, removing the need for manual bounds checking in your own code.
13+
14+
For validation that goes beyond simple ranges, you can write your own parser function and pass it to
15+
`value_parser`. The `config` field uses `parse_config`, which checks that the given path actually
16+
exists as a file. Similarly, the `notify` field uses `parse_email`, which performs a basic
17+
structural check on the input. Refer to this [`recipe`] for concrete email validation. Both
18+
functions return a `Result<T, String>`, where the error message becomes part of the output Clap
19+
shows the user when validation fails, making it clear which argument was invalid and why.
20+
21+
> This recipe requires the [`derive`] feature flag to be enabled in `Cargo.toml`.
22+
23+
```rust,edition2024,no_run
24+
use clap::{Parser, value_parser};
25+
use std::path::PathBuf;
26+
27+
#[derive(Debug, Parser)]
28+
struct CliArgs {
29+
/// Path to scan for cleanup
30+
#[arg(short = 'p', long = "path")]
31+
path: PathBuf,
32+
33+
/// Free space until usage falls below this percentage
34+
#[arg(short = 't', long = "threshold", value_parser = value_parser!(u8).range(0..=100))]
35+
threshold: u8,
36+
37+
/// Path to config file
38+
#[arg(short = 'c', long = "config", value_parser = parse_config)]
39+
config: PathBuf,
40+
41+
/// Email to notify on completion
42+
#[arg(short = 'n', long = "notify", value_parser = parse_email)]
43+
notify: String,
44+
}
45+
46+
fn parse_email(s: &str) -> Result<String, String> {
47+
match s.split_once('@') {
48+
Some((user, domain)) if !user.is_empty() && domain.contains('.') => Ok(s.to_string()),
49+
_ => Err("Invalid email format".to_string()),
50+
}
51+
}
52+
53+
fn parse_config(s: &str) -> Result<PathBuf, String> {
54+
let path = PathBuf::from(s);
55+
if path.is_file() {
56+
Ok(path)
57+
} else {
58+
Err("Provided config path is not a file".to_string())
59+
}
60+
}
61+
62+
fn main() {
63+
let args = CliArgs::parse();
64+
dbg!(args);
65+
}
66+
```
67+
68+
[`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html
69+
[`derive`]: https://docs.rs/clap/*/clap/_features/index.html
70+
[`recipe`]: https://rust-lang-nursery.github.io/rust-cookbook/text/regex.html#verify-and-extract-login-from-an-email-address
71+
[`value_parser`]: https://docs.rs/clap/*/clap/macro.value_parser.html

0 commit comments

Comments
 (0)