17 releases
0.4.0-pre.2 | Oct 31, 2024 |
---|---|
0.4.0-pre.1 | Apr 29, 2024 |
0.3.2 | Dec 18, 2023 |
0.3.1 | Oct 22, 2022 |
0.2.1 | Mar 6, 2021 |
#63 in Command-line interface
37,318 downloads per month
Used in 22 crates
(19 directly)
21KB
259 lines
xflags
provides a procedural macro for parsing command line arguments.
It is intended for use in development tools, so it emphasizes fast compile times and convenience at the expense of features.
Rough decision tree for picking an argument parsing library:
- if you need all of the features and don't care about minimalism, use clap
- if you want to be maximally minimal, need only basic features (eg, no help generation), and want to be pedantically correct, use lexopt
- if you want to get things done fast (eg, you want auto help, but not at the cost of waiting for syn to compile), consider this crate.
The secret sauce of xflags is that it is the opposite of a derive macro.
Rather than generating a command line grammar from a Rust struct, xflags
generates Rust structs based on input grammar. The grammar definition is
both shorter and simpler to write, and is lighter on compile times.
Here's a complete example of parse_or_exit!
macro which parses arguments
into an "anonymous" struct:
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 { "" },
)
}
The above program, when run with --help
argument, generates the following
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)
For larger programs, you'd typically want to use xflags!
macro, which
generates named structs for you. Unlike a typical macro, xflags
writes
generated code into the source file, to make it easy to understand the rust
types at a glance.
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);
}
If you'd rather use a typical proc-macro which generates hidden code, just omit the src attribute.
xflags correctly handles non-utf8 arguments.
Syntax Reference
The xflags!
macro uses cmd keyword to introduce a command or
subcommand that accepts positional arguments and switches.
xflags::xflags! {
cmd command-name { }
}
Switches are specified inside the curly braces. Long names (--switch
) are
mandatory, short names (-s
) are optional. Each switch can be optional,
required, or repeated. Dashes are allowed in switch names.
xflags::xflags! {
cmd switches {
optional -q,--quiet
required --pass-me
repeated --verbose
}
}
Switches can also take values. If the value type is OsString
or PathBuf
,
it is created directly from the underlying argument. Otherwise, FromStr
is
used for parsing
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
}
}
Arguments without --
in then are are positional.
use std::{path::PathBuf, ffi::OsString};
xflags::xflags! {
cmd positional-arguments {
required program: PathBuf
repeated args: OsString
}
}
You can create aliases if desired, which is as simple as adding extra names to the cmd
definition.
In this case, run
can be called as run
, r
and exec
:
xflags::xflags! {
cmd run r exec {}
}
Nesting cmd is allowed. xflag
automatically generates boilerplate
enums for subcommands:
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
Switches are always "inherited". Both app -v foo
and app foo -v
produce
the same result.
To make subcommand name optional use the default keyword to mark a subcommand to select if no subcommand name is passed. The name of the default subcommand affects only the name of the generated Rust struct, it can't be specified explicitly on the command line.
xflags::xflags! {
cmd app {
repeated -v, --verbose
default cmd foo { optional -s, --switch }
cmd bar {}
}
}
Commands, arguments, and switches can be documented. Doc comments become a part of generated help:
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()
}
}
The src keyword controls how the code generation works. If it is absent,
xflags
acts as a typical procedure macro, which generates a bunch of
structs and impls.
If the src keyword is present, it should specify the path to the file
with xflags!
invocation. The path should be relative to the directory with
Cargo.toml. The macro then will avoid generating the structs. Instead, if
the UPDATE_XFLAGS
environmental variable is set, the macro will write them
directly to the specified file.
By convention, xflag!
macro should be invoked from the flags
submodule.
The flags::
prefix should be used to refer to command names. Additional
validation logic can go to the flags
module:
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(())
}
}
}
The parse_or_exit!
macro is a syntactic sure for xflags!
, which
immediately parses the argument, exiting the process if needed.
parse_or_exit
only supports single top-level command and doesn't need the
cmd
keyword.
Limitations
xflags
follows
Fuchsia
conventions for command line arguments. GNU conventions such as grouping
short-flags (-xyz
) or gluing short flag and a value (-fVAL)
are not
supported.
xflags
requires the command line interface to be fully static. It's
impossible to include additional flags at runtime.
Implementation is not fully robust, there might be some residual bugs in edge cases.