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 个 工具包使用

MIT 许可证

58KB
1K SLoC

大兵

build status license version

大兵是一个小巧且具有偏见的参数解析器。它使用巧妙的技巧,使解析参数尽可能快且不痛苦。它没有依赖项,减少了冗余,因此减少了构建时间。以下是与行业标准clap的一些不同之处

  • 无依赖项
    • 导致体积小:与 clap 的 5.5MiB 相比,大小为 264KiB *(浅克隆 git 仓库 | du -h
    • 导致快速构建:与 clap 的 7s 相比,为 0.4,清洁构建*(在桌面和良好的 WiFi 上)
  • 没有 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 fmtcargo clippycargo 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(),
            ]
        )))
    );
}

没有运行时依赖

特性