21 个稳定版本 (5 个主要版本)
7.2.4 | 2024年5月29日 |
---|---|
7.0.2 | 2024年1月5日 |
7.0.1 | 2023年11月27日 |
6.0.2 | 2023年11月23日 |
2.0.3 | 2023年3月27日 |
#86 在 命令行界面
被 3 个 工具包使用
58KB
1K SLoC
大兵
大兵是一个小巧且具有偏见的参数解析器。它使用巧妙的技巧,使解析参数尽可能快且不痛苦。它没有依赖项,减少了冗余,因此减少了构建时间。以下是与行业标准clap的一些不同之处
- 无依赖项
- 导致体积小:与 clap 的
5.5MiB
相比,大小为264KiB
*(浅克隆 git 仓库 |du -h
) - 导致快速构建:与 clap 的
7s
相比,为0.4
,清洁构建*(在桌面和良好的 WiFi 上)
- 导致体积小:与 clap 的
- 没有 proc 宏
- 通过默认功能
macros
提供强大的 正则 宏
- 通过默认功能
- 提供更干净的构建器-like 接口
- 不是全能的
- 不支持奇怪的语法
- 所有结构式参数都必须有长格式
- 专注于合理的默认值,以最大程度地减少每个人的努力
- 不提供帮助信息、补全等
- 不支持嵌套参数
- 不是由委员会管理的
- 这不是出于厌恶,但只有一个维护者,所以...
- 不是一个庞大的项目
- 那些可以很棒,但也可以过度设计
- 支持自定义参数类型的第一类支持
*免责声明:这些数字可能不是最新的,但 sarge 一侧不应有任何重大变化。
上述其中之一可能是一个决定性的因素。没关系!我制作了 sarge,以便有一个良好的、轻量级的 clap 替代方案。使用任何适合您的用例即可。我个人在我的所有项目中使用 sarge,因为它们都是小的;这迫使我积极维护它。
特性
- 无依赖项(是的,这是我最喜欢的功能)
- 一流的 "builder" 模式,但更好
- 曾经是唯一的选择,因此已经得到了详细说明
- 用于构建CLI界面的非宏方法
- 支持默认值
- 支持环境变量
- 自定义参数类型
- 简单实现一个特性和内建功能一样
- 以下内置参数类型
bool
i8/i16/i32/i64/i128/isize
u8/u16/u32/u64/u128/usize
f32/f64
String
Vec<T>
其中T: ArgumentType
购物清单
- 更好的单元测试
- 对于所有内容都有测试,但它们还不是优先级最高的
- 更多的维护者
- 更好的代码风格
- 可能移除
clippy::pedantic
并获得更精细的控制
- 可能移除
- 更好的、更完整的文档
- 我希望它们是顶级的
贡献
上述内容主要来自两个原因:单个维护者,以及对项目的兴趣不足。如果你使用sarge,请 在GitHub上给它加星,或者更好的是,留下问题!这告诉我其他人对这个项目感兴趣,并推动我更加严谨地开发它。
至于单个维护者,我乐于接受拉取请求。只需确保它通过 cargo fmt
, cargo clippy
和 cargo test
即可。某些功能可能不在sarge的范围内;目标不是无限的定制性,因此如果某个功能显著增加了复杂性,它可能不会被接受。
示例
这是一个使用所有功能的巨大示例;注意,如果你禁用了 macros
功能,这个示例将无法编译
use sarge::prelude::*;
// This is a normal, non-proc macro. That means sarge is still
// zero-dependency! The syntax may seem a little strange at first, but it
// should help greatly when defining your CLI interface.
sarge! {
// This is the name of our struct.
Args,
// These are our arguments. Each will have a long variant matching the
// field name one-to-one, with one exception: all underscores are
// replaced by dashes at compile-time.
//
// The hashtags denote the arg 'wrapper'. No wrapper means it will be
// unwrapped; if the argument wasn't passed, or it failed to parse, this
// will panic. Thankfully, `bool` arguments are immune to both, and
// `String` arguments are immune to the latter.
first: bool, // true if `--first` is passed, false otherwise
// If you want a short variant (e.g. '-s'), you can specify one with a char
// literal before the name (but after the wrapper, if any):
's' second: String,
// You can also specify an environment variable counterpart. If an argument
// has values for both an environment variable and a CLI argument, the CLI
// argument takes precedence.
@ENV_VAR env_var: i32,
// `#err` makes the argument an `Option<Result<T, _>>`.
#err foo: f32,
// `#ok` makes the argument an `Option<T>`, discarding any parsing errors.
#ok bar: f64,
// Here's every feature in one argument: a `Result<T, _>` that can be set
// via `-b`, `--baz`, or `BAZ=`, and defaults to [1, 2, 3] if not passed.
#err 'b' @BAZ baz: Vec<u64> = vec![1, 2, 3],
}
// Some utility macros to make this example less verbose.
macro_rules! create_args {
( $( $arg:expr ),* $(,)? ) => {
[ $( $arg.to_string(), )* ]
};
}
macro_rules! create_env {
( $( $name:expr, $val:expr ),* $(,)? ) => {
[ $( ($name.to_string(), $val.to_string()), )* ]
};
}
fn main() {
let args = create_args![
"test", // Usually the name of the executable.
"--first",
"-s", "Hello, World!",
"--bar=badnum", // The syntax `--arg=val` is valid for long tags.
"foobar", // This value isn't part of an argument.
"--baz", "1,2,3", // Remember this value...
];
let env = create_env![
"ENV_VAR", "42",
"BAZ", "4,5,6", // ...and this one.
];
// Normally, you would use `::parse()` here. However, since this gets run
// as a test, we'll manually pass the arguments along.
let (args, remainder) = Args::parse_provided(&args, env.into_iter())
.expect("Failed to parse arguments");
assert_eq!(remainder, vec!["test", "foobar"]);
assert!(args.first);
assert_eq!(args.second, "Hello, World!");
assert_eq!(args.env_var, 42);
assert_eq!(args.foo, None);
assert_eq!(args.bar, None);
assert_eq!(args.baz, Ok(vec![1, 2, 3]));
}
环境变量
Sarge 还支持使用环境变量作为参数。在调用 parse
时会自动完成,或者你可以使用 parse_env
来自己传递变量。
这里有一个快速示例
use sarge::prelude::*;
fn main() {
let mut parser = ArgumentReader::new();
// This can only be specified via environment variable.
let just_env = parser.add(tag::env("JUST_ENV"));
// This can be specified as either an environment variable,
// or a regular CLI argument. If both are given, the CLI
// argument takes precedence.
let both = parser.add(tag::long("cli-form").env("ENV_FORM"));
// Here are the CLI arguments...
let cli_args = [
"test".to_string(),
"--cli-form=123".to_string(),
];
// ...and the "environment" variables.
let env_args = [
// Boolean arguments treat `0`, `false`, and no argument as false,
// while everything else is true.
("JUST_ENV".to_string(), "0".to_string()),
("ENV_FORM".to_string(), "456".to_string()),
].into_iter();
// `parser.parse()` would automatically use `std::env::vars`.
let args = parser.parse_provided(&cli_args, env_args).unwrap();
// `args` has the type `Arguments`, which contains two things:
// - The CLI arguments that weren't part of a tagged argument
// - The tagged arguments and their values
//
// To get a value from an `ArgumentRef`, use `.get(&Arguments)`:
assert_eq!(just_env.get(&args), Some(Ok(false)));
// Since the CLI argument was given, it uses that instead.
assert_eq!(both.get(&args), Some(Ok(123i64)));
}
自定义类型
使用 ArgumentType
特性,你可以实现自己的类型。以下是一个示例(来自 src/test/custom_type.rs
)
use std::convert::Infallible;
use sarge::{prelude::*, ArgumentType, ArgResult};
#[derive(Debug, PartialEq, Eq)]
struct MyCustomType(Vec<String>);
impl ArgumentType for MyCustomType {
/// This gets returned from `ArgumentRef::get` in the event
/// of a failed parse.
type Error = Infallible;
/// Do your parsing here. This just splits on spaces.
/// If the argument was passed without a value, `val == None`.
fn from_value(val: Option<&str>) -> ArgResult<Self> {
Some(Ok(Self(
val?.split(' ')
.map(|s| s.to_string())
.collect()
)))
}
}
sarge! {
Args,
#err my_argument: MyCustomType,
}
fn main() {
let arguments = [
"custom_type_test".to_string(),
"--my-argument".to_string(),
"Hello World !".to_string(),
];
let (args, _) = Args::parse_provided(&arguments, None::<(&str, &str)>).expect("failed to parse arguments");
assert_eq!(
args.my_argument,
Some(Ok(MyCustomType(
vec![
"Hello".to_string(),
"World".to_string(),
"!".to_string(),
]
)))
);
}