青蛙小白
目录

clap

clap是一个Rust命令行参数解析器。使用clap可以在编写命令行程序时,以声明式或过程式的方式解析命令行参数和子命令。

快速链接

快速示例

cargo add clap --features derive
use clap::Parser;

/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    #[arg(short, long)]
    name: String,
    #[arg(short, long, default_value_t = 1)]
    count: u8,
}

fn main() {
    let args = Args::parse();
    for _ in 0..args.count {
        println!("Hello {}!", args.name);
    }
}
$ demo --help
A simple to use, efficient, and full-featured Command Line Argument Parser

Usage: demo[EXE] [OPTIONS] --name <NAME>

Options:
  -n, --name <NAME>    Name of the person to greet
  -c, --count <COUNT>  Number of times to greet [default: 1]
  -h, --help           Print help
  -V, --version        Print version

$ demo --name Me
Hello Me!

枚举值

如果要测试特定值的参数,可以派生ValueEnum(任何PossibleValue构建器函数都可以用作枚举变体上的#[value]属性)。

这允许指定该参数的有效值。如果用户没有使用其中一个特定值,他们将收到一个优雅的退出,并显示错误消息,告知他们错误是什么以及可能的有效值是什么。

use clap::{Parser, ValueEnum};

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    /// What mode to run the program in
    #[arg(value_enum)]
    mode: Mode,
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Mode {
    /// Run swiftly
    Fast,
    /// Crawl slowly but steadily
    ///
    /// This paragraph is ignored because there is no long help text for possible values.
    Slow,
}

fn main() {
    let cli = Cli::parse();

    match cli.mode {
        Mode::Fast => {
            println!("Hare");
        }
        Mode::Slow => {
            println!("Tortoise");
        }
    }
}
$ 04_01_enum_derive --help
A simple to use, efficient, and full-featured Command Line Argument Parser

Usage: 04_01_enum_derive[EXE] <MODE>

Arguments:
  <MODE>
          What mode to run the program in

          Possible values:
          - fast: Run swiftly
          - slow: Crawl slowly but steadily

Options:
  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

$ 04_01_enum_derive -h
A simple to use, efficient, and full-featured Command Line Argument Parser

Usage: 04_01_enum_derive[EXE] <MODE>

Arguments:
  <MODE>  What mode to run the program in [possible values: fast, slow]

Options:
  -h, --help     Print help (see more with '--help')
  -V, --version  Print version

$ 04_01_enum_derive fast
Hare

$ 04_01_enum_derive slow
Tortoise

$ 04_01_enum_derive medium
? failed
error: invalid value 'medium' for '<MODE>'
  [possible values: fast, slow]

For more information, try '--help'.

验证值

可以使用Arg::value_parser验证并解析任何数据类型。

use clap::Parser;

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    /// Network port to use
    #[arg(value_parser = clap::value_parser!(u16).range(1..))]
    port: u16,
}

fn main() {
    let cli = Cli::parse();

    println!("PORT = {}", cli.port);
}
$ 04_02_parse_derive --help
A simple to use, efficient, and full-featured Command Line Argument Parser

Usage: 04_02_parse_derive[EXE] <PORT>

Arguments:
  <PORT>  Network port to use

Options:
  -h, --help     Print help
  -V, --version  Print version

$ 04_02_parse_derive 22
PORT = 22

$ 04_02_parse_derive foobar
? failed
error: invalid value 'foobar' for '<PORT>': invalid digit found in string

For more information, try '--help'.

$ 04_02_parse_derive 0
? failed
error: invalid value '0' for '<PORT>': 0 is not in 1..=65535

For more information, try '--help'.

自定义解析器可以用于改进错误消息或提供额外的验证:

use std::ops::RangeInclusive;

use clap::Parser;

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    /// Network port to use
    #[arg(value_parser = port_in_range)]
    port: u16,
}

fn main() {
    let cli = Cli::parse();

    println!("PORT = {}", cli.port);
}

const PORT_RANGE: RangeInclusive<usize> = 1..=65535;

fn port_in_range(s: &str) -> Result<u16, String> {
    let port: usize = s
        .parse()
        .map_err(|_| format!("`{s}` isn't a port number"))?;
    if PORT_RANGE.contains(&port) {
        Ok(port as u16)
    } else {
        Err(format!(
            "port not in range {}-{}",
            PORT_RANGE.start(),
            PORT_RANGE.end()
        ))
    }
}
$ 04_02_validate_derive --help
A simple to use, efficient, and full-featured Command Line Argument Parser

Usage: 04_02_validate_derive[EXE] <PORT>

Arguments:
  <PORT>  Network port to use

Options:
  -h, --help     Print help
  -V, --version  Print version

$ 04_02_validate_derive 22
PORT = 22

$ 04_02_validate_derive foobar
? failed
error: invalid value 'foobar' for '<PORT>': `foobar` isn't a port number

For more information, try '--help'.

$ 04_02_validate_derive 0
? failed
error: invalid value '0' for '<PORT>': port not in range 1-65535

For more information, try '--help'.

参数关系

可以声明Args或甚至ArgGroups之间的依赖关系或冲突。

ArgGroups使得更容易声明关系,而不是必须单独列出每个关系,或者当希望规则适用于“任何但不全部”参数时。

也许ArgGroups最常见的用法是要求一组给定的参数中只有一个参数必须存在。想象一下,有多个参数,并且希望其中一个参数是必需的,但是使所有参数都必需是不可能的,因为它们可能相互冲突。

ArgGroups是为具有ArgGroup::id为结构体名称的结构体自动创建的。

use clap::{Args, Parser};

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    #[command(flatten)]
    vers: Vers,

    /// some regular input
    #[arg(group = "input")]
    input_file: Option<String>,

    /// some special input argument
    #[arg(long, group = "input")]
    spec_in: Option<String>,

    #[arg(short, requires = "input")]
    config: Option<String>,
}

#[derive(Args)]
#[group(required = true, multiple = false)]
struct Vers {
    /// set version manually
    #[arg(long, value_name = "VER")]
    set_ver: Option<String>,

    /// auto inc major
    #[arg(long)]
    major: bool,

    /// auto inc minor
    #[arg(long)]
    minor: bool,

    /// auto inc patch
    #[arg(long)]
    patch: bool,
}

fn main() {
    let cli = Cli::parse();

    // Let's assume the old version 1.2.3
    let mut major = 1;
    let mut minor = 2;
    let mut patch = 3;

    // See if --set_ver was used to set the version manually
    let vers = &cli.vers;
    let version = if let Some(ver) = vers.set_ver.as_deref() {
        ver.to_string()
    } else {
        // Increment the one requested (in a real program, we'd reset the lower numbers)
        let (maj, min, pat) = (vers.major, vers.minor, vers.patch);
        match (maj, min, pat) {
            (true, _, _) => major += 1,
            (_, true, _) => minor += 1,
            (_, _, true) => patch += 1,
            _ => unreachable!(),
        };
        format!("{major}.{minor}.{patch}")
    };

    println!("Version: {version}");

    // Check for usage of -c
    if let Some(config) = cli.config.as_deref() {
        let input = cli
            .input_file
            .as_deref()
            .unwrap_or_else(|| cli.spec_in.as_deref().unwrap());
        println!("Doing work using input {input} and config {config}");
    }
}
$ 04_03_relations_derive --help
A simple to use, efficient, and full-featured Command Line Argument Parser

Usage: 04_03_relations_derive[EXE] [OPTIONS] <--set-ver <VER>|--major|--minor|--patch> [INPUT_FILE]

Arguments:
  [INPUT_FILE]  some regular input

Options:
      --set-ver <VER>      set version manually
      --major              auto inc major
      --minor              auto inc minor
      --patch              auto inc patch
      --spec-in <SPEC_IN>  some special input argument
  -c <CONFIG>              
  -h, --help               Print help
  -V, --version            Print version

$ 04_03_relations_derive
? failed
error: the following required arguments were not provided:
  <--set-ver <VER>|--major|--minor|--patch>

Usage: 04_03_relations_derive[EXE] <--set-ver <VER>|--major|--minor|--patch> [INPUT_FILE]

For more information, try '--help'.

$ 04_03_relations_derive --major
Version: 2.2.3

$ 04_03_relations_derive --major --minor
? failed
error: the argument '--major' cannot be used with '--minor'

Usage: 04_03_relations_derive[EXE] <--set-ver <VER>|--major|--minor|--patch> [INPUT_FILE]

For more information, try '--help'.

$ 04_03_relations_derive --major -c config.toml
? failed
error: the following required arguments were not provided:
  <INPUT_FILE|--spec-in <SPEC_IN>>

Usage: 04_03_relations_derive[EXE] -c <CONFIG> <--set-ver <VER>|--major|--minor|--patch> <INPUT_FILE|--spec-in <SPEC_IN>>

For more information, try '--help'.

$ 04_03_relations_derive --major -c config.toml --spec-in input.txt
Version: 2.2.3
Doing work using input input.txt and config config.toml

自定义验证

可以使用clap的基本格式创建自定义错误。

use clap::error::ErrorKind;
use clap::{CommandFactory, Parser};

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    /// set version manually
    #[arg(long, value_name = "VER")]
    set_ver: Option<String>,

    /// auto inc major
    #[arg(long)]
    major: bool,

    /// auto inc minor
    #[arg(long)]
    minor: bool,

    /// auto inc patch
    #[arg(long)]
    patch: bool,

    /// some regular input
    input_file: Option<String>,

    /// some special input argument
    #[arg(long)]
    spec_in: Option<String>,

    #[arg(short)]
    config: Option<String>,
}

fn main() {
    let cli = Cli::parse();

    // Let's assume the old version 1.2.3
    let mut major = 1;
    let mut minor = 2;
    let mut patch = 3;

    // See if --set-ver was used to set the version manually
    let version = if let Some(ver) = cli.set_ver.as_deref() {
        if cli.major || cli.minor || cli.patch {
            let mut cmd = Cli::command();
            cmd.error(
                ErrorKind::ArgumentConflict,
                "Can't do relative and absolute version change",
            )
            .exit();
        }
        ver.to_string()
    } else {
        // Increment the one requested (in a real program, we'd reset the lower numbers)
        let (maj, min, pat) = (cli.major, cli.minor, cli.patch);
        match (maj, min, pat) {
            (true, false, false) => major += 1,
            (false, true, false) => minor += 1,
            (false, false, true) => patch += 1,
            _ => {
                let mut cmd = Cli::command();
                cmd.error(
                    ErrorKind::ArgumentConflict,
                    "Can only modify one version field",
                )
                .exit();
            }
        };
        format!("{major}.{minor}.{patch}")
    };

    println!("Version: {version}");

    // Check for usage of -c
    if let Some(config) = cli.config.as_deref() {
        let input = cli
            .input_file
            .as_deref()
            // 'or' is preferred to 'or_else' here since `Option::as_deref` is 'const'
            .or(cli.spec_in.as_deref())
            .unwrap_or_else(|| {
                let mut cmd = Cli::command();
                cmd.error(
                    ErrorKind::MissingRequiredArgument,
                    "INPUT_FILE or --spec-in is required when using --config",
                )
                .exit()
            });
        println!("Doing work using input {input} and config {config}");
    }
}
$ 04_04_custom_derive --help
A simple to use, efficient, and full-featured Command Line Argument Parser

Usage: 04_04_custom_derive[EXE] [OPTIONS] [INPUT_FILE]

Arguments:
  [INPUT_FILE]  some regular input

Options:
      --set-ver <VER>      set version manually
      --major              auto inc major
      --minor              auto inc minor
      --patch              auto inc patch
      --spec-in <SPEC_IN>  some special input argument
  -c <CONFIG>              
  -h, --help               Print help
  -V, --version            Print version

$ 04_04_custom_derive
? failed
error: Can only modify one version field

Usage: clap [OPTIONS] [INPUT_FILE]

For more information, try '--help'.

$ 04_04_custom_derive --major
Version: 2.2.3

$ 04_04_custom_derive --major --minor
? failed
error: Can only modify one version field

Usage: clap [OPTIONS] [INPUT_FILE]

For more information, try '--help'.

$ 04_04_custom_derive --major -c config.toml
? failed
Version: 2.2.3
error: INPUT_FILE or --spec-in is required when using --config

Usage: clap [OPTIONS] [INPUT_FILE]

For more information, try '--help'.

$ 04_04_custom_derive --major -c config.toml --spec-in input.txt
Version: 2.2.3
Doing work using input input.txt and config config.toml

Example: git-like CLI (Derive API)

参考

评论