clap #
clap是一个Rust命令行参数解析器。使用clap可以在编写命令行程序时,以声明式或过程式的方式解析命令行参数和子命令。
快速链接 #
快速示例 #
1cargo add clap --features derive
1use clap::Parser;
2
3/// Simple program to greet a person
4#[derive(Parser, Debug)]
5#[command(version, about, long_about = None)]
6struct Args {
7 #[arg(short, long)]
8 name: String,
9 #[arg(short, long, default_value_t = 1)]
10 count: u8,
11}
12
13fn main() {
14 let args = Args::parse();
15 for _ in 0..args.count {
16 println!("Hello {}!", args.name);
17 }
18}
1$ demo --help
2A simple to use, efficient, and full-featured Command Line Argument Parser
3
4Usage: demo[EXE] [OPTIONS] --name <NAME>
5
6Options:
7 -n, --name <NAME> Name of the person to greet
8 -c, --count <COUNT> Number of times to greet [default: 1]
9 -h, --help Print help
10 -V, --version Print version
11
12$ demo --name Me
13Hello Me!
枚举值 #
如果要测试特定值的参数,可以派生ValueEnum
(任何PossibleValue
构建器函数都可以用作枚举变体上的#[value]
属性)。
这允许指定该参数的有效值。如果用户没有使用其中一个特定值,他们将收到一个优雅的退出,并显示错误消息,告知他们错误是什么以及可能的有效值是什么。
1use clap::{Parser, ValueEnum};
2
3#[derive(Parser)]
4#[command(version, about, long_about = None)]
5struct Cli {
6 /// What mode to run the program in
7 #[arg(value_enum)]
8 mode: Mode,
9}
10
11#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
12enum Mode {
13 /// Run swiftly
14 Fast,
15 /// Crawl slowly but steadily
16 ///
17 /// This paragraph is ignored because there is no long help text for possible values.
18 Slow,
19}
20
21fn main() {
22 let cli = Cli::parse();
23
24 match cli.mode {
25 Mode::Fast => {
26 println!("Hare");
27 }
28 Mode::Slow => {
29 println!("Tortoise");
30 }
31 }
32}
1$ 04_01_enum_derive --help
2A simple to use, efficient, and full-featured Command Line Argument Parser
3
4Usage: 04_01_enum_derive[EXE] <MODE>
5
6Arguments:
7 <MODE>
8 What mode to run the program in
9
10 Possible values:
11 - fast: Run swiftly
12 - slow: Crawl slowly but steadily
13
14Options:
15 -h, --help
16 Print help (see a summary with '-h')
17
18 -V, --version
19 Print version
20
21$ 04_01_enum_derive -h
22A simple to use, efficient, and full-featured Command Line Argument Parser
23
24Usage: 04_01_enum_derive[EXE] <MODE>
25
26Arguments:
27 <MODE> What mode to run the program in [possible values: fast, slow]
28
29Options:
30 -h, --help Print help (see more with '--help')
31 -V, --version Print version
32
33$ 04_01_enum_derive fast
34Hare
35
36$ 04_01_enum_derive slow
37Tortoise
38
39$ 04_01_enum_derive medium
40? failed
41error: invalid value 'medium' for '<MODE>'
42 [possible values: fast, slow]
43
44For more information, try '--help'.
验证值 #
可以使用Arg::value_parser
验证并解析任何数据类型。
1use clap::Parser;
2
3#[derive(Parser)]
4#[command(version, about, long_about = None)]
5struct Cli {
6 /// Network port to use
7 #[arg(value_parser = clap::value_parser!(u16).range(1..))]
8 port: u16,
9}
10
11fn main() {
12 let cli = Cli::parse();
13
14 println!("PORT = {}", cli.port);
15}
1$ 04_02_parse_derive --help
2A simple to use, efficient, and full-featured Command Line Argument Parser
3
4Usage: 04_02_parse_derive[EXE] <PORT>
5
6Arguments:
7 <PORT> Network port to use
8
9Options:
10 -h, --help Print help
11 -V, --version Print version
12
13$ 04_02_parse_derive 22
14PORT = 22
15
16$ 04_02_parse_derive foobar
17? failed
18error: invalid value 'foobar' for '<PORT>': invalid digit found in string
19
20For more information, try '--help'.
21
22$ 04_02_parse_derive 0
23? failed
24error: invalid value '0' for '<PORT>': 0 is not in 1..=65535
25
26For more information, try '--help'.
自定义解析器可以用于改进错误消息或提供额外的验证:
1use std::ops::RangeInclusive;
2
3use clap::Parser;
4
5#[derive(Parser)]
6#[command(version, about, long_about = None)]
7struct Cli {
8 /// Network port to use
9 #[arg(value_parser = port_in_range)]
10 port: u16,
11}
12
13fn main() {
14 let cli = Cli::parse();
15
16 println!("PORT = {}", cli.port);
17}
18
19const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
20
21fn port_in_range(s: &str) -> Result<u16, String> {
22 let port: usize = s
23 .parse()
24 .map_err(|_| format!("`{s}` isn't a port number"))?;
25 if PORT_RANGE.contains(&port) {
26 Ok(port as u16)
27 } else {
28 Err(format!(
29 "port not in range {}-{}",
30 PORT_RANGE.start(),
31 PORT_RANGE.end()
32 ))
33 }
34}
1$ 04_02_validate_derive --help
2A simple to use, efficient, and full-featured Command Line Argument Parser
3
4Usage: 04_02_validate_derive[EXE] <PORT>
5
6Arguments:
7 <PORT> Network port to use
8
9Options:
10 -h, --help Print help
11 -V, --version Print version
12
13$ 04_02_validate_derive 22
14PORT = 22
15
16$ 04_02_validate_derive foobar
17? failed
18error: invalid value 'foobar' for '<PORT>': `foobar` isn't a port number
19
20For more information, try '--help'.
21
22$ 04_02_validate_derive 0
23? failed
24error: invalid value '0' for '<PORT>': port not in range 1-65535
25
26For more information, try '--help'.
参数关系 #
可以声明Args
或甚至ArgGroups
之间的依赖关系或冲突。
ArgGroups
使得更容易声明关系,而不是必须单独列出每个关系,或者当希望规则适用于“任何但不全部”参数时。
也许ArgGroups
最常见的用法是要求一组给定的参数中只有一个参数必须存在。想象一下,有多个参数,并且希望其中一个参数是必需的,但是使所有参数都必需是不可能的,因为它们可能相互冲突。
ArgGroups
是为具有ArgGroup::id
为结构体名称的结构体自动创建的。
1use clap::{Args, Parser};
2
3#[derive(Parser)]
4#[command(version, about, long_about = None)]
5struct Cli {
6 #[command(flatten)]
7 vers: Vers,
8
9 /// some regular input
10 #[arg(group = "input")]
11 input_file: Option<String>,
12
13 /// some special input argument
14 #[arg(long, group = "input")]
15 spec_in: Option<String>,
16
17 #[arg(short, requires = "input")]
18 config: Option<String>,
19}
20
21#[derive(Args)]
22#[group(required = true, multiple = false)]
23struct Vers {
24 /// set version manually
25 #[arg(long, value_name = "VER")]
26 set_ver: Option<String>,
27
28 /// auto inc major
29 #[arg(long)]
30 major: bool,
31
32 /// auto inc minor
33 #[arg(long)]
34 minor: bool,
35
36 /// auto inc patch
37 #[arg(long)]
38 patch: bool,
39}
40
41fn main() {
42 let cli = Cli::parse();
43
44 // Let's assume the old version 1.2.3
45 let mut major = 1;
46 let mut minor = 2;
47 let mut patch = 3;
48
49 // See if --set_ver was used to set the version manually
50 let vers = &cli.vers;
51 let version = if let Some(ver) = vers.set_ver.as_deref() {
52 ver.to_string()
53 } else {
54 // Increment the one requested (in a real program, we'd reset the lower numbers)
55 let (maj, min, pat) = (vers.major, vers.minor, vers.patch);
56 match (maj, min, pat) {
57 (true, _, _) => major += 1,
58 (_, true, _) => minor += 1,
59 (_, _, true) => patch += 1,
60 _ => unreachable!(),
61 };
62 format!("{major}.{minor}.{patch}")
63 };
64
65 println!("Version: {version}");
66
67 // Check for usage of -c
68 if let Some(config) = cli.config.as_deref() {
69 let input = cli
70 .input_file
71 .as_deref()
72 .unwrap_or_else(|| cli.spec_in.as_deref().unwrap());
73 println!("Doing work using input {input} and config {config}");
74 }
75}
1$ 04_03_relations_derive --help
2A simple to use, efficient, and full-featured Command Line Argument Parser
3
4Usage: 04_03_relations_derive[EXE] [OPTIONS] <--set-ver <VER>|--major|--minor|--patch> [INPUT_FILE]
5
6Arguments:
7 [INPUT_FILE] some regular input
8
9Options:
10 --set-ver <VER> set version manually
11 --major auto inc major
12 --minor auto inc minor
13 --patch auto inc patch
14 --spec-in <SPEC_IN> some special input argument
15 -c <CONFIG>
16 -h, --help Print help
17 -V, --version Print version
18
19$ 04_03_relations_derive
20? failed
21error: the following required arguments were not provided:
22 <--set-ver <VER>|--major|--minor|--patch>
23
24Usage: 04_03_relations_derive[EXE] <--set-ver <VER>|--major|--minor|--patch> [INPUT_FILE]
25
26For more information, try '--help'.
27
28$ 04_03_relations_derive --major
29Version: 2.2.3
30
31$ 04_03_relations_derive --major --minor
32? failed
33error: the argument '--major' cannot be used with '--minor'
34
35Usage: 04_03_relations_derive[EXE] <--set-ver <VER>|--major|--minor|--patch> [INPUT_FILE]
36
37For more information, try '--help'.
38
39$ 04_03_relations_derive --major -c config.toml
40? failed
41error: the following required arguments were not provided:
42 <INPUT_FILE|--spec-in <SPEC_IN>>
43
44Usage: 04_03_relations_derive[EXE] -c <CONFIG> <--set-ver <VER>|--major|--minor|--patch> <INPUT_FILE|--spec-in <SPEC_IN>>
45
46For more information, try '--help'.
47
48$ 04_03_relations_derive --major -c config.toml --spec-in input.txt
49Version: 2.2.3
50Doing work using input input.txt and config config.toml
自定义验证 #
可以使用clap的基本格式创建自定义错误。
1use clap::error::ErrorKind;
2use clap::{CommandFactory, Parser};
3
4#[derive(Parser)]
5#[command(version, about, long_about = None)]
6struct Cli {
7 /// set version manually
8 #[arg(long, value_name = "VER")]
9 set_ver: Option<String>,
10
11 /// auto inc major
12 #[arg(long)]
13 major: bool,
14
15 /// auto inc minor
16 #[arg(long)]
17 minor: bool,
18
19 /// auto inc patch
20 #[arg(long)]
21 patch: bool,
22
23 /// some regular input
24 input_file: Option<String>,
25
26 /// some special input argument
27 #[arg(long)]
28 spec_in: Option<String>,
29
30 #[arg(short)]
31 config: Option<String>,
32}
33
34fn main() {
35 let cli = Cli::parse();
36
37 // Let's assume the old version 1.2.3
38 let mut major = 1;
39 let mut minor = 2;
40 let mut patch = 3;
41
42 // See if --set-ver was used to set the version manually
43 let version = if let Some(ver) = cli.set_ver.as_deref() {
44 if cli.major || cli.minor || cli.patch {
45 let mut cmd = Cli::command();
46 cmd.error(
47 ErrorKind::ArgumentConflict,
48 "Can't do relative and absolute version change",
49 )
50 .exit();
51 }
52 ver.to_string()
53 } else {
54 // Increment the one requested (in a real program, we'd reset the lower numbers)
55 let (maj, min, pat) = (cli.major, cli.minor, cli.patch);
56 match (maj, min, pat) {
57 (true, false, false) => major += 1,
58 (false, true, false) => minor += 1,
59 (false, false, true) => patch += 1,
60 _ => {
61 let mut cmd = Cli::command();
62 cmd.error(
63 ErrorKind::ArgumentConflict,
64 "Can only modify one version field",
65 )
66 .exit();
67 }
68 };
69 format!("{major}.{minor}.{patch}")
70 };
71
72 println!("Version: {version}");
73
74 // Check for usage of -c
75 if let Some(config) = cli.config.as_deref() {
76 let input = cli
77 .input_file
78 .as_deref()
79 // 'or' is preferred to 'or_else' here since `Option::as_deref` is 'const'
80 .or(cli.spec_in.as_deref())
81 .unwrap_or_else(|| {
82 let mut cmd = Cli::command();
83 cmd.error(
84 ErrorKind::MissingRequiredArgument,
85 "INPUT_FILE or --spec-in is required when using --config",
86 )
87 .exit()
88 });
89 println!("Doing work using input {input} and config {config}");
90 }
91}
1$ 04_04_custom_derive --help
2A simple to use, efficient, and full-featured Command Line Argument Parser
3
4Usage: 04_04_custom_derive[EXE] [OPTIONS] [INPUT_FILE]
5
6Arguments:
7 [INPUT_FILE] some regular input
8
9Options:
10 --set-ver <VER> set version manually
11 --major auto inc major
12 --minor auto inc minor
13 --patch auto inc patch
14 --spec-in <SPEC_IN> some special input argument
15 -c <CONFIG>
16 -h, --help Print help
17 -V, --version Print version
18
19$ 04_04_custom_derive
20? failed
21error: Can only modify one version field
22
23Usage: clap [OPTIONS] [INPUT_FILE]
24
25For more information, try '--help'.
26
27$ 04_04_custom_derive --major
28Version: 2.2.3
29
30$ 04_04_custom_derive --major --minor
31? failed
32error: Can only modify one version field
33
34Usage: clap [OPTIONS] [INPUT_FILE]
35
36For more information, try '--help'.
37
38$ 04_04_custom_derive --major -c config.toml
39? failed
40Version: 2.2.3
41error: INPUT_FILE or --spec-in is required when using --config
42
43Usage: clap [OPTIONS] [INPUT_FILE]
44
45For more information, try '--help'.
46
47$ 04_04_custom_derive --major -c config.toml --spec-in input.txt
48Version: 2.2.3
49Doing work using input input.txt and config config.toml