clap

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

Example: git-like CLI (Derive API) #

参考 #

© 2024 青蛙小白