2 个不稳定版本

0.5.0 2024年2月13日
0.4.0 2022年7月6日

#200 in 命令行界面


用于 mapiproxy

MIT 许可证

59KB
885

ArgSplitter - 解析命令行参数的辅助包

辅助包用于解析命令行参数

例如 clap 这样的包允许您创建一个功能丰富的命令行解析器。例如,

  • 自动将配置解析到结构体中,
  • 生成帮助文本,
  • 或者甚至从帮助文本开始,从中推导出命令行解析器。

缺点是,为了覆盖所有可能性,API 必须相当大且复杂,并且您需要做很多学习、阅读文档和调整才能使其满足您的需求。此外,您可能需要重新学习所有这些内容,每当您想更改旧项目或开始新项目时。

ArgSplitter 包的主要目标是,在一段时间未使用后,只需几分钟即可重新投入生产。它试图通过使用 Rust 的 match 语句来简化处理命令行标志的过程,并尝试帮助正确处理具有无效 Unicode 编码的参数。因此,它仅提供以下服务

  1. 将组合的单短划线标志(如 -xvf)拆分为单独的标志 -x-v-f

  2. 处理类似 -fbanana--fruit=banana 的标志参数。后者可能与 --fruit banana 等价,也可能不等价。

  3. 正确处理非 Unicode 参数(如文件名),同时尽可能使用常规字符串。这是因为 Unix 和 Windows 都允许无法表示为 UTF-8 编码字符串的文件名。

关于编码的说明:第3项很重要,因为Rust字符串被定义为UTF-8编码,但Unix和Windows都允许文件名和命令行参数不是Unicode。对于这些,Rust提供了OsString,它不如String方便使用,但可以表示一切。在argsplitter API中,以_os结尾的方法的返回类型基于OsString,而其他方法基于String。根据需要,您可以在这些变体之间来回切换。

概述

我们区分短选项、长选项和单词。短选项以单个短横线开头,可以组合使用,因此-xvf-x -v -f等效。长选项,如--file,以两个短横线开头,总是包含一个单字母标志。单词是不以短横线开头的参数。有时它们是独立的参数,有时它们是先前标志的参数。长选项也可以附加参数,例如--file=data.csv

使用argsplitter crate制作的解析器不是声明性的,而是纯过程性的。首先,您构建一个ArgSplitter,然后您重复调用方法item()item_os()param()param_os()flag()从命令行消耗选项和单词。

示例

use argsplitter::{main_support, ArgError, ArgSplitter};
use std::{error::Error, path::PathBuf, process::ExitCode};

const USAGE: &str = r###"
Usage: send_mail [OPTIONS..] RECIPIENT..
Options:
   -v   --verbose          Describe what's going on
   -a   --attach FILE      Attach this file
   -s   --subject TEXT     Subject: line
   -h   --help             Print this help
"###;

fn main() -> ExitCode {
    main_support::report_errors(USAGE, work())
}

fn work() -> Result<(), Box<dyn Error>> {
    // To be configured using arguments
    let mut verbose = false;
    let mut subject: Option<String> = None;
    let mut attachments: Vec<PathBuf> = vec![];

    let mut argsplitter = ArgSplitter::from_env();

    // .flag() skips non-flag arguments and stashes them for later use.
    while let Some(flag) = argsplitter.flag()? {
        match flag {
            "-h" | "--help" => {
                println!("{}", USAGE.trim());
                return Err(ArgError::exit_successfully())?;
            }

            "-v" | "--verbose" => verbose = true,

            // subject is a String so we use .param()
            "-s" | "--subject" => subject = Some(argsplitter.param()?),

            // attachment is a file name  so we use .param_os()
            "-a" | "--attach" => attachments.push(argsplitter.param_os()?.into()),

            flag => return Err(ArgError::unknown_flag(flag))?,
        }
    }

    // Pick up the recipients stashed by .flag().
    // The first argument states the minimum number that must be present.
    // The second argument is used in the error messages.
    let recipients: Result<Vec<_>, _> = argsplitter.stashed_args(1, "RECIPIENTS").collect();
    // Handle ArgError::ArgumentMissing and ArgError::InvalidUnicode
    let recipients = recipients?;

    println!("verbose={verbose}");
    println!("subject={subject:?}");
    println!("recipients={recipients:?}");
    println!("attachments={attachments:?}");
    Ok(())
}

示例输出

使用-h和--help,用法输出到stdout

» send_mail -h
-- stdout --
Usage: send_mail [OPTIONS..] RECIPIENT..
Options:
   -v   --verbose          Describe what's going on
   -a   --attach FILE      Attach this file
   -s   --subject TEXT     Subject: line
   -h   --help             Print this help

没有任何参数时,它会向stderr抱怨

» send_mail
-- stderr --
Error: missing argument: RECIPIENTS
Usage: send_mail [OPTIONS..] RECIPIENT..
Options:
   -v   --verbose          Describe what's going on
   -a   --attach FILE      Attach this file
   -s   --subject TEXT     Subject: line
   -h   --help             Print this help
-- exit status 1

非标志参数是接收者,至少必须有一个

» send_mail alice bob
-- stdout --
verbose=false
subject=None
recipients=["alice", "bob"]
attachments=[]

它注意到"verbose"(详细)

» send_mail alice -v bob
-- stdout --
verbose=true
subject=None
recipients=["alice", "bob"]
attachments=[]

如果我们组合多个标志,它也能正常工作

» send_mail alice -vshello bob
-- stdout --
verbose=true
subject=Some("Hello")
recipients=["alice", "bob"]
attachments=[]

没有运行时依赖项