2 个不稳定版本
0.5.0 | 2024年2月13日 |
---|---|
0.4.0 | 2022年7月6日 |
#200 in 命令行界面
用于 mapiproxy
59KB
885 行
ArgSplitter - 解析命令行参数的辅助包
辅助包用于解析命令行参数
例如 clap 这样的包允许您创建一个功能丰富的命令行解析器。例如,
- 自动将配置解析到结构体中,
- 生成帮助文本,
- 或者甚至从帮助文本开始,从中推导出命令行解析器。
缺点是,为了覆盖所有可能性,API 必须相当大且复杂,并且您需要做很多学习、阅读文档和调整才能使其满足您的需求。此外,您可能需要重新学习所有这些内容,每当您想更改旧项目或开始新项目时。
ArgSplitter 包的主要目标是,在一段时间未使用后,只需几分钟即可重新投入生产。它试图通过使用 Rust 的 match
语句来简化处理命令行标志的过程,并尝试帮助正确处理具有无效 Unicode 编码的参数。因此,它仅提供以下服务
-
将组合的单短划线标志(如
-xvf
)拆分为单独的标志-x
、-v
和-f
。 -
处理类似
-fbanana
或--fruit=banana
的标志参数。后者可能与--fruit banana
等价,也可能不等价。 -
正确处理非 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=[]