16 个版本

0.4.0-pre.12024年4月29日
0.3.2 2023年12月18日
0.3.1 2022年10月22日
0.2.4 2022年3月2日
0.1.4 2021年3月5日

#81命令行界面

Download history 8345/week @ 2024-05-02 7602/week @ 2024-05-09 8552/week @ 2024-05-16 8675/week @ 2024-05-23 8401/week @ 2024-05-30 8782/week @ 2024-06-06 9169/week @ 2024-06-13 9289/week @ 2024-06-20 8165/week @ 2024-06-27 8340/week @ 2024-07-04 8544/week @ 2024-07-11 8885/week @ 2024-07-18 8913/week @ 2024-07-25 8899/week @ 2024-08-01 8825/week @ 2024-08-08 7319/week @ 2024-08-15

35,259 每月下载量
19 库中使用(17 个直接使用)

MIT/Apache

21KB
259

xflags 提供了解析命令行参数的过程宏。

它适用于开发工具,因此它强调快速的编译时间和便利性,而牺牲了一些功能。

选择参数解析库的粗略决策树

  • 如果你需要所有功能并且不关心简约主义,请使用 clap
  • 如果你想尽可能简约,只需要基本功能(例如,不需要帮助生成),并且想做到一丝不苟,请使用 lexopt
  • 如果你想快速完成任务(例如,你想自动生成帮助,但不想因为等待 syn 编译而等待),请考虑这个库。

xflags 的秘密在于它是一个与 derive 宏相反的宏。它不是从 Rust 结构体生成命令行语法,而是根据输入语法生成 Rust 结构体。语法定义既简短又简单,编译时间也更短。

以下是 parse_or_exit! 宏的完整示例,该宏将参数解析到“匿名”结构体中

use std::path::PathBuf;

fn main() {
    let flags = xflags::parse_or_exit! {
        /// Remove directories and their contents recursively.
        optional -r,--recursive
        /// File or directory to remove
        required path: PathBuf
    };

    println!(
        "removing {}{}",
        flags.path.display(),
        if flags.recursive { "recursively" } else { "" },
    )
}

上述程序在带有 --help 参数运行时,会生成以下帮助信息

Usage:  <path> [-r] [-h]
Arguments:
  <path>               File or directory to remove

Options:
  -r, --recursive      Remove directories and their contents recursively.
  -h, --help           Prints help

Commands:
  help                 Print this message or the help of the given subcommand(s)

对于较大的程序,通常希望使用 xflags! 宏,该宏为您生成 命名 结构体。与典型宏不同,xflags 将生成的代码写入源文件,以便您可以轻松了解 rust 类型。

mod flags {
    use std::path::PathBuf;

    xflags::xflags! {
        src "./examples/basic.rs"

        cmd my-command {
            required path: PathBuf
            optional -v, --verbose
        }
    }

    // generated start
    // The following code is generated by `xflags` macro.
    // Run `env UPDATE_XFLAGS=1 cargo build` to regenerate.
    #[derive(Debug)]
    pub struct MyCommand {
        pub path: PathBuf,
        pub verbose: bool,
    }

    impl MyCommand {
        pub fn from_env_or_exit() -> Self {
            Self::from_env_or_exit_()
        }
        pub fn from_env() -> xflags::Result<Self> {
            Self::from_env_()
        }
        pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {
            Self::from_vec_(args)
        }
    }
   // generated end
}

fn main() {
    let flags = flags::MyCommand::from_env();
    println!("{:#?}", flags);
}

如果您希望使用生成隐藏代码的典型 proc-macro,请省略 src 属性。

xflags 正确处理非 utf8 参数。

语法参考

xflags! 宏使用 cmd 关键字来引入接受位置参数和开关的命令或子命令。

xflags::xflags! {
    cmd command-name { }
}

开关指定在花括号内。长名称(--switch)是必须的,短名称(-s)是可选的。每个开关可以是 可选的必需的重复的。开关名称中允许使用破折号。

xflags::xflags! {
    cmd switches {
        optional -q,--quiet
        required --pass-me
        repeated --verbose
    }
}

开关还可以接受值。如果值类型是 OsStringPathBuf,则直接从底层参数创建。否则,使用 FromStr 进行解析

use std::{path::PathBuf, ffi::OsString};

xflags::xflags! {
    cmd switches-with-values {
        optional --config path: PathBuf
        repeated --data val: OsString
        optional -j, --jobs n: u32
    }
}

没有 -- 的参数是位置参数。

use std::{path::PathBuf, ffi::OsString};

xflags::xflags! {
    cmd positional-arguments {
        required program: PathBuf
        repeated args: OsString
    }
}

如果需要,可以创建别名,这就像在 cmd 定义中添加额外的名称一样简单。在这种情况下,run 可以被称为 runrexec

xflags::xflags! {
    cmd run r exec {}
}

允许嵌套 cmdxflag 自动为子命令生成样板枚举

xflags::xflags! {
    src "./examples/subcommands.rs"
    cmd app {
        repeated -v, --verbose
        cmd foo { optional -s, --switch }
        cmd bar {}
    }
}

// generated start
// The following code is generated by `xflags` macro.
// Run `env UPDATE_XFLAGS=1 cargo build` to regenerate.
#[derive(Debug)]
pub struct App {
    pub verbose: u32,
    pub subcommand: AppCmd,
}

#[derive(Debug)]
pub enum AppCmd {
    Foo(Foo),
    Bar(Bar),
}

#[derive(Debug)]
pub struct Foo {
    pub switch: bool,
}

#[derive(Debug)]
pub struct Bar {
}

impl App {
    pub fn from_env_or_exit() -> Self {
        Self::from_env_or_exit_()
    }
    pub fn from_env() -> xflags::Result<Self> {
        Self::from_env_()
    }
    pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {
        Self::from_vec_(args)
    }
}
// generated end

开关总是“继承的”。无论是 app -v foo 还是 app foo -v,结果都是一样的。

要使子命令名称可选,使用 default 关键字标记子命令,以选择是否未传递子命令名称。默认子命令的名称仅影响生成的 Rust 结构体的名称,不能在命令行上显式指定。

xflags::xflags! {
    cmd app {
        repeated -v, --verbose
        default cmd foo { optional -s, --switch }
        cmd bar {}
    }
}

命令、参数和开关都可以进行文档说明。文档注释成为生成帮助的一部分

mod flags {
    use std::path::PathBuf;

    xflags::xflags! {
        /// Run basic system diagnostics.
        cmd healthck {
            /// Optional configuration file.
            optional config: PathBuf
            /// Verbosity level, can be repeated multiple times.
            repeated -v, --verbose
        }
    }
}

fn main() {
    match flags::Healthck::from_env() {
        Ok(flags) => {
            run_checks(flags.config, flags.verbose);
        }
        Err(err) => err.exit()
    }
}

关键字 src 控制代码生成的方式。如果它不存在,xflags 作为典型的过程宏,生成大量结构和实现。

如果存在 src 关键字,它应该指定包含 xflags! 调用的文件的路径。路径应该是相对于包含 Cargo.toml 的目录的相对路径。然后,宏将避免生成结构。相反,如果设置了 UPDATE_XFLAGS 环境变量,则宏将直接将其写入指定的文件。

按照惯例,xflag! 宏应从 flags 子模块调用。应使用 flags:: 前缀来引用命令名称。额外的验证逻辑可以放在 flags 模块中

mod flags {
    xflags::xflags! {
        cmd my-command {
            repeated -v, --verbose
            optional -q, --quiet
        }
    }

    impl MyCommand {
        fn validate(&self) -> xflags::Result<()> {
            if self.quiet && self.verbose > 0 {
                return Err(xflags::Error::new(
                    "`-q` and `-v` can't be specified at the same time"
                ));
            }
            Ok(())
        }
    }
}

parse_or_exit! 宏是 xflags! 的语法糖,它立即解析参数,如果需要则退出进程。 parse_or_exit 只支持单个顶级命令,不需要 cmd 关键字。

限制

xflags 遵循 Fuchsia 命令行参数约定。不支持像分组短标志(-xyz)或粘合短标志和值((-fVAL))这样的 GNU 约定。

xflags 要求命令行界面完全静态。无法在运行时包含额外的标志。

实现不是完全健壮的,边缘情况下可能存在一些残留错误。

依赖