2 个版本
0.0.3 | 2020年12月13日 |
---|---|
0.0.2 |
|
0.0.1 | 2020年12月13日 |
#763 在 命令行界面
9KB
197 行
type-cli
type-cli
是一个方便、强类型的命令行界面解析器。
让我们先为 grep
创建一个界面。
基本用法
use type_cli::CLI;
#[derive(CLI)]
struct Grep(String, String);
fn main() {
let Grep(pattern, file) = Grep::process();
let pattern = regex::Regex::new(&pattern).unwrap();
eprintln!("Searching for `{}` in {}", pattern, file);
}
现在,如果我们用参数运行二进制文件,它们将被正确解析。如果我们遗漏了一个参数,它会给出有用的错误。
$ grep foo* myFile
Searching for `foo*` in myFile
$ grep foo*
Expected an argument at position `2`
然而,这并不是一个忠实的 grep 界面:在 grep 中,文件是可选的。此外,那个 unwrap()
看起来有点糟糕。
use type_cli::CLI;
#[derive(CLI)]
struct Grep(regex::Regex, #[optional] Option<String>);
fn main() {
match Grep::process() {
Grep(pattern, Some(file)) => eprintln!("Serching for `{}` in {}", pattern, file),
Grep(pattern, None) => eprintln!("Searching for `{}` in stdin", pattern),
}
}
那是什么?我们正在直接接受一个 Regex
作为参数?在 type-cli
中,任何实现了 FromStr
的类型都可以作为参数。任何解析错误都会优雅地返回给用户,无需你担心。
$ grep foo(
Error parsing positional argument `1`:
regex parse error:
foo(
^
error: unclosed group
在这里,你还可以看到必须使用 #[optional]
来注释可选参数。
$ grep foo* myFile
Serching for `foo*` in myFile
$ grep foo*
Searching for `foo*` in stdin
这个界面 仍然 并不完全忠实;grep 允许多个文件进行搜索。
use type_cli::CLI;
#[derive(CLI)]
struct Grep(regex::Regex, #[variadic] Vec<String>);
fn main(){
let Grep(pattern, file_list) = Grep::process();
if file_list.is_empty() {
eprintln!("Searching for `{}` in stdin", pattern);
} else {
eprint!("Searching for `{}` in ", pattern);
file_list.iter().for_each(|f| eprint!("{}, ", f));
}
}
如果你在最后一个字段上注释 #[variadic]
,它将解析任意数量的参数。这对于任何实现了 FromIterator
的集合都有效。
$ grep foo*
Searching for `foo*` in stdin
$grep foo* myFile yourFile ourFile
Searching for `foo*` in myFile, yourFile, ourFile,
尽管如此,这仍然不是理想的解决方案。没有任何字段有名字,也没有标志或选项!显然,元组结构体限制了我们的能力。
命名参数和标志
use type_cli::CLI;
#[derive(CLI)]
struct Grep {
pattern: regex::Regex,
#[named]
file: String,
#[flag(short = "i")]
ignore_case: bool,
}
fn main() {
let Grep { pattern, file, ignore_case } = Grep::process();
eprint!("Searching for `{}` in {}", pattern, file);
if ignore_case {
eprint!(", ignoring case");
}
eprintln!();
}
命名参数使用 #[named]
注释,并允许它们以任何顺序传递给命令。默认情况下,命名参数仍然是必需的,但也可以用 #[optional]
标记。
$ grep foo*
Expected an argument named `--file`
$ grep foo* --file myFile
Searching for `foo*` in myFile
标志使用 #[flag]
进行注释,并且是完全可选的布尔或整数标志。您可以使用 #[flag(short = "a")]
(此形式也适用于命名参数)指定简短形式。
$ grep foo* --file myFile --ignore-case
Searching for `foo*` in myFile, ignoring case
$ grep foo* --file myFile -i
Searching for `foo*` in myFile, ignoring case
这似乎很好,但如果我想在我的应用程序中使用多个命令呢?
子命令
use type_cli::CLI;
#[derive(CLI)]
enum Cargo {
New(String),
Build {
#[named] #[optional]
target: Option<String>,
#[flag]
release: bool,
},
Clippy {
#[flag]
pedantic: bool,
}
}
fn main() {
match Cargo::process() {
Cargo::New(name) => eprintln!("Creating new crate `{}`", name),
Cargo::Build { target, release } => {
let target = target.as_deref().unwrap_or("windows");
if release {
eprintln!("Building for {} in release", target);
} else {
eprintln!("Building for {}", target);
}
}
Cargo::Clippy { pedantic: true } => eprintln!("Annoyingly checking your code."),
Cargo::Clippy { pedantic: false } => eprintln!("Checking your code."),
}
}
如果您从一个枚举派生 CLI
,则每个变体将表示一个子命令。每个子命令都使用与之前相同的语法进行解析。
Rust 的 Pascal 风格命名将自动转换为壳的标准形式: SubCommand
-> sub-command
$ cargo new myCrate
Creating new crate `myCrate`
$ cargo build
Building for windows
$ cargo build --target linux
Building for linux
$ cargo build --target linux --release
Building for linux in release
$ cargo clippy
Checking your code.
$ cargo clippy --pedantic
Annoyingly checking your code.
关于文档呢?
--help
use type_cli::CLI;
#[derive(CLI)]
#[help = "Build manager tool for rust"]
enum Cargo {
New(String),
#[help = "Build the current crate."]
Build {
#[named] #[optional]
#[help = "the target platform"]
target: Option<String>,
#[flag]
#[help = "build for release mode"]
release: bool,
},
#[help = "Lint your code"]
Clippy {
#[flag]
#[help = "include annoying and subjective lints"]
pedantic: bool,
}
}
type-cli
将自动为您生成的命令生成帮助屏幕。如果您对子命令或参数使用 #[help = ""]
注释,它将包括您的简短描述。显示时,它将被发送到标准错误,并且进程将以非零状态退出。
$ cargo
Help - cargo
Build manager tool for rust
SUBCOMMANDS:
new
build Build the current crate.
clippy Lint your code
对于枚举,如果没有指定子命令调用命令,将显示此内容。
$ cargo build --help
Help - build
Build the current crate.
ARGUMENTS:
--target the target platform [optional]
FLAGS:
--release build for release mode
$ cargo clippy -h
Help - clippy
Lint your code
FLAGS:
--pedantic include annoying and subjective lints
对于结构体或子命令,如果传递了标志 --help
或 -h
,将调用此内容。目前不支持为元组结构体提供帮助信息。
依赖关系
~3.5–5MB
~97K SLoC