6 个版本

0.1.5 2021年5月27日
0.1.4 2021年5月2日
0.1.3 2021年4月25日

#961 in 命令行界面


rout 中使用

Apache-2.0 许可协议

110KB
2.5K SLoC

ap - 参数解析器

概述

ap 是一个用于解析命令行参数的 Rust 包(crate)。

它体积小、简单,但也有一些有趣的功能。

详细说明

请参阅[此处](https://docs.rs/ap)的文档。


lib.rs:

简单的命令行参数解析 crate。

如果您需要更多功能,请考虑使用优秀的 clap crate。

要了解“简单”的含义,请参阅限制部分。


目录


概述

此 crate 用于解析命令行参数。它为每个解析的已注册选项调用一个处理函数。

快速入门

注意:如果您不熟悉命令行处理,请参阅术语部分。

  1. 创建一个表示将用于处理 所有 选项的处理器的 struct 类型。

    如果只想在特定选项指定时收到通知,则该结构可以为空;或者您可以指定成员以记录或计算特定细节。

    #[derive(Clone, Debug, Default)]
    struct MyHandler {}
    
  2. 为该 struct 实现一个 [Handler] 特性。

    此特性仅要求您创建一个返回 [Result] 的单个 handle() 方法,以指示成功或失败。

    #
    #
    impl Handler for &mut MyHandler {
        fn handle(&mut self, arg: Arg) -> Result<()> {
            // ...
    
            Ok(())
        }
    }
    
  3. 为您的 struct 类型创建一个处理程序变量。

    #
    #
    #
    #
    let mut handler = MyHandler::default();
    
  4. 创建一个 [Args] 变量以保存您希望支持的 所有 参数。

    #
    let mut args = Args::default();
    
  5. 将您希望支持的每个参数添加到 [Args] 变量中的新 [Arg]。

    至少,您必须指定参数的“名称”(单字符短选项值)。

    默认情况下,选项是“标志”(请参阅术语部分)。

    #
    #
    // Support "-a <value>" option.
    args.add(Arg::new('a').needs(Need::Argument));
    
    // Support "-d" flag option.
    args.add(Arg::new('d'));
    
  6. 创建一个表示程序的变量 [App],并指定 [Args] 和 [Handler] 变量

    #
    #
    #
    #
    #
    #
    #
    let mut args = App::new("my app")
        .help("some text")
        .args(args)
        .handler(Box::new(&mut handler));
    
  7. 在 [App] 变量上调用 parse() 方法。对于添加到 [Args] 变量的所有 [Arg] 参数,都会调用处理器

    #
    #
    #
    #
    #
    #
    #
    #
    // Parse the command-line
    let result = args.parse();
    

示例

以下是一个完整的示例,展示了如何编写支持一些命令行选项的程序。它还展示了处理器如何修改其状态,从而实现具有状态和条件选项处理。

use ap::{App, Arg, Args, Handler, Need, Result};

// The type that will be used to handle all the CLI options
// for this program.
#[derive(Clone, Debug, Default)]
struct MyHandler {
    i: usize,
    v: Vec<String>,
    s: String,
}

impl Handler for &mut MyHandler {
    fn handle(&mut self, arg: Arg) -> Result<()> {
        println!(
            "option: {:?}, value: {:?}, count: {}",
            arg.option, arg.value, arg.count
        );

        // Change behaviour if user specified '-d'
        if arg.option == 'd' {
            self.i += 7;
        } else {
            self.i += 123;
        }

        self.s = "string value set by handler".into();
        self.v.push("vector modified by handler".into());

        Ok(())
    }
}

fn main() -> Result<()> {
    let mut handler = MyHandler::default();

    println!("Initial state of handler: {:?}", handler);

    let mut args = Args::default();

    // Support "-a <value>" option.
    args.add(Arg::new('a').needs(Need::Argument));

    // Support "-b <value>" option.
    args.add(Arg::new('b').needs(Need::Argument));

    // Support "-d" flag option.
    args.add(Arg::new('d'));

    let mut args = App::new("my app")
        .help("some text")
        .args(args)
        .handler(Box::new(&mut handler));

    // Parse the command-line
    let result = args.parse();

    // If you want to inspect the handler after parsing, you need to
    // force ownership to be returned by dropping the `Args` variable.
    drop(args);

    println!("Final state of handler: {:?}", handler);

    // return value
    result
}

对于更多示例,请尝试 examples/ 目录中的程序

$ cargo run --example simple -- -a foo -d -a bar -d -a baz
$ cargo run --example positional-args-only -- one two "hello world" three "foo bar" four "the end"
$ cargo run --example option-and-positional-args -- "posn 1" -d "posn 2" -a "hello world" -a "foo bar" "the end" -d
$ cargo run --example error-handler -- -a -e -i -o -u

细节

术语

注意:有关更多详细信息,请参阅 getopt(3)

  • “参数”是指在命令行上传递给程序的值。

    参数可以是“选项”或“位置参数”。

    注意:单引号或双引号内的字符串被视为一个参数,即使该字符串包含多个单词(这种魔法由 shell 处理)。

  • “选项”是以下划线字符(-)开头并以单个字符结尾的参数,该字符本身不是 -,例如,-a-z-A-Z-0-9等等

    该字符是选项的“名称”。选项名称区分大小写:大写和小写字母代表不同的选项。

    这种类型的选项被称为“短选项”,因为它只由一个字符识别。

  • 接受参数(一个称为“选项参数”或“optarg”的值)的选项通常简单地称为“选项”,因为这是最常见的选项形式。

  • “选项参数”是紧跟在选项后面的值。它被认为是“绑定”或与前面的选项配对。根据定义,选项参数不能以下划线开头,以避免被视为自己的选项。

  • 不接受参数的选项称为“标志”或“独立选项”。这些通常用于切换某些功能的开启或关闭。

    标志示例

    大多数程序支持一些常见的标志

    • -h:显示帮助/使用说明并退出。
    • -v:显示版本号并退出,或有时启用详细模式。
  • “位置参数”(也称为“非选项参数”)不是选项的参数:它是一个单词或一个引号内的字符串(该字符串不能以下划线开头,除非它被转义为 \-)。

    位置参数示例

    echo(1) 是处理位置参数的程序的良例

    $ echo one two three "hello, world" four five "the end"
    
  • 特殊选项 -- 保留用于表示“所有选项的结束”:它可以由需要接受一组选项后跟一组位置参数的程序使用。即使双横线后的参数以下划线开头,它也不会被视为选项。

    如果该包在命令行上找到 --,它将停止处理命令行参数。

参数类型示例

假设一个程序按照以下方式运行:

$ myprog -d 371 "hello, world" -x "awesome value" -v "the end"

该程序有7个实际的命令行参数。

1: '-d'
2: '371'
3: 'hello, world'
4: '-x'
5: 'awesome value'
6: '-v'
7: 'the end'

这些参数的解析方式取决于每个选项(以-开头的参数)是否指定了取值。

如果所有选项都指定为标志,则参数的解析方式如下:

'-d'            # A flag option.
'371'           # A positional argument.
'hello, world'  # A positional argument.
'-x'            # A flag option.
'awesome value' # A positional argument.
'-v'            # A flag option.
'the end'       # A positional argument.

但是,如果我们假设-d-x被指定为取值,那么参数分组如下:

'-d 371'           # An option ('d') with a numeric option argument ('371').
'hello, world'     # A positional argument ('hello, world').
'-x awesome value' # An option ('x') with a string option argument ('awesome value').
'-v'               # A flag option.
'the end'          # A positional argument.

或者,如果我们假设所有选项都取值,那么参数分组如下:

'-d 371'             # An option ('d') with a numeric option argument ('371').
'hello, world'       # A positional argument ('hello, world').
'-x 'awesome value'' # An option ('x') with a string option argument ('awesome value').
'-v 'the end''       # An option('v') with a string option argument ('the end').

处理歧义

默认情况下,使用getopt(3)语义,这意味着在解析参数时没有歧义:如果声明了一个[Arg],指定了Need::Argument,则选项参数之后的下一个参数(无论是什么!)将被消耗并用作选项参数。

尽管没有歧义,但这对用户来说可能是个意外,因为其他(通常是较新的)命令行参数解析器以微妙不同的方式工作。例如,假设程序指定了以下内容:

#
let mut args = Args::default();

args.add(Arg::new('1'));
args.add(Arg::new('2').needs(Need::Argument));

如果程序随后按照以下方式调用...

$ prog -2 -1

...则-2选项的值将被设置为-1,而-1选项将假定未指定。这就是getopt(3)的工作方式。然而,这可能不是用户所期望的,或者程序员所希望的。

在解析上述命令行时,另一种替代策略是将选项视为比参数更重要,并在这种情况下报错,因为-2选项未提供参数(因为-1选项在有效选项参数之前指定了)。

有关此细微之处的更多详细信息,请参阅[App]或[Settings]的no_strict_options()方法。

原因

为什么还需要另一个命令行解析器呢?

有很多Rust命令行参数解析器。这个是写的,因为我找不到一个满足以下所有要求的crate:

  • 允许选项和非选项(“位置参数”)混合。

    这对于某些用例非常有用,并且在符合POSIX规范的libc实现中是标准库调用。引用自getopt(3)

    如果optstring的第一个字符是'-',则每个非选项argv元素都处理为具有字符码1的选项的参数。

  • 顺序解析命令行参数。

    现代时尚似乎是构建选项的哈希表,以便程序可以查询是否指定了选项。这在大多数情况下很有用,但我有一个需要顺序每个选项出现的次数重要的用例。

  • 允许指定一个处理函数来处理遇到的参数。

总结来说,我需要一个更类似POSIX的(POSIXLY_CORRECT)命令行参数解析器,所以,这就是它。

功能和行为的总结

  • 简单直观(“人体工程学”)的API。

  • 代码库小。

  • 全面的单元测试集。

  • 按严格顺序解析参数。

  • 立即处理每个参数。

    一旦遇到(注册和有效的)参数,就会调用处理程序。

  • 参数不会被重新排序。

  • 需要指定一个函数来处理每个选项。

  • 选项参数始终以字符串形式返回。

    调用者可以根据需要将它们转换为数字等。

  • 允许标志选项、带有参数的选项和“位置参数”(非选项参数)混合使用。

  • 允许多次指定选项(并记录该次数)。

    注意:您可以通过在处理程序中检查Arg.count值来限制发生次数。

  • 可以将选项定义为必选。

  • 可以配置未知选项为忽略或传递给处理程序。

    注意

    • 默认情况下,未知选项不会被处理,如果找到未知选项,将生成错误。
    • 如果您想支持位置参数,可以注册一个[Arg]用于[POSITIONAL_HANDLER_OPT],或者设置Settings.ignore_unknown_options
  • “未知”的位置参数可以配置为忽略或传递给处理程序。

    注意

    • 默认情况下,位置参数不会被处理,如果找到位置参数,将生成错误。
    • 如果您想支持位置参数,可以注册一个[Arg]用于[POSITIONAL_HANDLER_OPT],或者设置Settings.ignore_unknown_posn_args
  • 自动生成帮助/用法说明(-h)。

限制

  • 不支持选项捆绑

    示例: -d -v -a "foo bar" 是有效的,但 -dva "foo bar" 是无效的。

  • 不支持非ASCII选项名称。

  • 不支持长选项

    示例: -v 是有效的,但 --verbose 是无效的。

  • 选项及其参数必须由空白分隔。

    示例: '-d 3' 是有效的,但 '-d3' 是有效的。

  • 不支持带有可选参数的选项。

    说明:选项必须定义为标志(无参数)或标准选项(需要值)。不能同时是两者。

  • 选项不能接受多个值

    示例: -a "foo bar" "baz" "the end" 不能解析为一个单独的 -a 选项。

    然而

    • 选项可以指定多次,每次具有不同的值。
    • 您可以使用 [POSITIONAL_HANDLER_OPT] 解析该命令行。

依赖关系

~305–770KB
~18K SLoC