16 个稳定版本
2.1.0 | 2023年4月22日 |
---|---|
2.0.1 | 2023年4月22日 |
1.3.1 | 2023年4月21日 |
1.2.5 | 2022年4月14日 |
1.0.1 | 2021年11月28日 |
在 命令行界面 中排名 #355
被用于 fix-name-case
19KB
63 行代码(不含注释)
fncmd
是一个具有意见的命令行解析器前端,它包装了 clap
。其功能与 clap
大致相同,但提供了更自动化和集成的体验。
动机
想象一下你想要创建的命令行程序。本质上,它可以抽象为一个简单函数,该函数以命令行选项作为参数。那么,不应该有什么东西阻止你将其真正地写成函数,而不使用像今天的 Rustaceans 使用的结构体或构建器。
这个概念受到了 argopt
的极大启发,我非常感激这项工作。然而,它仍然需要一些繁琐的代码,特别是处理子命令时。 fncmd
已经从头开始重写,以消除所有复杂性。请查看 子命令 部分,了解我们如何处理它。
安装
这个 crate 仅限于 nightly 版本。在使用之前,请确保已将您的工具链设置为 nightly(例如,拥有 rust-toolchain
文件)。您可能对 为什么是 nightly 感兴趣。
要安装,请在您的项目目录中运行
cargo add fncmd
对于喜欢案例研究的人:请参阅 examples
。
基础知识
此 crate 仅公开了一个属性宏,fncmd
,该宏只能附加到 main
函数上
// main.rs
/// Description of the command line tool
#[fncmd::fncmd]
pub fn main(
/// Argument foo
#[opt(short, long)]
foo: String,
/// Argument bar
#[opt(short, long)]
bar: Option<String>,
) {
println!("{:?} {:?}", foo, bar);
}
就这么多,现在您已经得到了一个由 clap
处理选项的命令行程序。使用上面的代码,帮助信息如下
$ crate-name --help
Description of the command line tool
Usage: crate-name[EXE] [OPTIONS] --foo <FOO>
Options:
-f, --foo <FOO> Argument foo
-b, --bar <BAR> Argument bar
-h, --help Print help
-V, --version Print version
您的命令名称和版本将自动从 Cargo 元数据中推断。
opt
属性的使用几乎与底层 arg
属性 完全相同。它们直接传递,唯一的区别是如果没有提供配置,则隐含了 (long)
,即 #[opt]
表示 #[opt(long)]
。如果您想不带 --foo
就取 foo
参数,只需省略 #[opt]
。
子命令
如您所知,在 Cargo 项目中 您可以将其他二进制程序的入口点放入 src/bin
。如果 1) 它们的名称以 crate-name
前缀开头,2) 它们的 main
函数装饰了 #[fncmd]
属性,并且 3) 被公开为 pub
,那么这些将自动包装为默认二进制目标 crate-name
的子命令。假设您有以下目录结构
src
├── main.rs
└── bin
├── crate-name-subcommand1.rs
└── crate-name-subcommand2.rs
您将得到以下子命令结构
crate-name
├── crate-name subcommand1
└── crate-name subcommand2
多个命令和嵌套子命令
实际上,fncmd
并没有区分“默认”二进制和“附加”二进制。它仅基于前缀结构确定子命令结构。因此,在您的 Cargo.toml
中配置二进制目标应该按预期工作,例如
[[bin]]
name = "crate-name"
path = "src/clis/crate-name.rs"
[[bin]]
name = "another"
path = "src/clis/another.rs"
[[bin]]
name = "another-sub" # `pub`
path = "src/clis/another-sub.rs"
[[bin]]
name = "another-sub-subsub" # `pub`
path = "src/clis/another-sub-subsub.rs"
[[bin]]
name = "another-orphan" # non-`pub`
path = "src/clis/another-orphan.rs"
[[bin]]
name = "another-orphan-sub" # `pub`
path = "src/clis/another-orphan-sub.rs"
此配置生成以下命令
crate-name
another
└── another sub
└── another sub subsub
another-orphan
└── another-orphan sub
请注意,another-orphan
不包含在 another
中,因为它没有被公开为 pub
。如上图所示,使 main
不是 pub
仅当您希望它与其他程序有共同的 prefixes 但不希望被其他程序包含时才有意义,因此在大多数情况下,您可以设置 pub
而无需考虑。
当然,也可以在不手动编辑 Cargo.toml
的情况下达到相同的结构,只需将文件放入默认位置
src
├── main.rs
└── bin
├── another.rs
├── another-sub.rs
├── another-sub-subsub.rs
├── another-orphan.rs
└── another-orphan-sub.rs
与异构属性宏一起使用
有时您可能想使用其他属性宏,如 #[tokio::main]
和 #[async_std::main]
来转换 main
函数。在这种情况下,您必须将 #[fncmd]
放在最高级别
/// Description of the command line tool
#[fncmd]
#[tokio::main]
pub async fn main(hello: String) -> anyhow::Result<()> {
...
}
但是不要这样做
/// Description of the command line tool
#[tokio::main]
#[fncmd]
pub async fn main(hello: String) -> anyhow::Result<()> {
...
}
这是因为像 #[tokio::main]
这样的宏会在其内部进行一些断言,因此我们需要向它们提供一个良好的 main
函数版本,例如删除参数。
文档注释的位置并不重要。
限制
fncmd
设计上不会支持以下功能。这就是为什么 fncmd
被称为“有偏见的”。
无法在帮助信息中显示作者
在帮助信息中显示作者在普通用户看来只会增加噪音。
无法更改命令的名称和版本为不同的值
将元数据(如 name
和 version
)更改为与 Cargo.toml
中定义的不同值,很容易破坏其可维护性和一致性。
无法将 #[fncmd]
附接到 main
之外的函数
将 #[fncmd]
附接到任意函数会导致单个文件代码库膨胀,这在一般情况下应该避免。
为什么是 nightly
自动确定哪些目标是子命令或不是子命令的方式需要 #[fncmd]
宏本身知道附加的目标名称,以及调用它的文件的路径。这可以通过 Span::source_file
实现,这背后是一个不稳定的特性标志 proc_macro_span
。
依赖
~4MB
~79K SLoC