xflags-0.3.1/.cargo_vcs_info.json0000644000000001530000000000100123200ustar { "git": { "sha1": "5b33711a3813e15564f7b7855d3c8406669f599f" }, "path_in_vcs": "crates/xflags" }xflags-0.3.1/Cargo.lock0000644000000006000000000000100102700ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "xflags" version = "0.3.1" dependencies = [ "xflags-macros", ] [[package]] name = "xflags-macros" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f58e7b3ca8977093aae6b87b6a7730216fc4c53a6530bab5c43a783cd810c1a8" xflags-0.3.1/Cargo.toml0000644000000015000000000000100103130ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "xflags" version = "0.3.1" authors = ["Aleksey Kladov "] description = "Moderately simple command line arguments parser." categories = ["command-line-interface"] license = "MIT OR Apache-2.0" repository = "https://github.com/matklad/xflags" resolver = "1" [dependencies.xflags-macros] version = "=0.3.1" xflags-0.3.1/Cargo.toml.orig000064400000000000000000000005261046102023000140030ustar 00000000000000[package] name = "xflags" description = "Moderately simple command line arguments parser." categories = ["command-line-interface"] version.workspace = true license.workspace = true repository.workspace = true authors.workspace = true edition.workspace = true [dependencies] xflags-macros = { path = "../xflags-macros", version = "=0.3.1" } xflags-0.3.1/examples/hello-generated.rs000064400000000000000000000021501046102023000163320ustar 00000000000000mod flags { #![allow(unused)] xflags::xflags! { src "./examples/hello-generated.rs" /// Prints a greeting. cmd hello { /// Whom to greet. required name: String /// Use non-ascii symbols in the output. optional -e, --emoji } } // generated start // The following code is generated by `xflags` macro. // Run `env UPDATE_XFLAGS=1 cargo build` to regenerate. #[derive(Debug)] pub struct Hello { pub name: String, pub emoji: bool, } impl Hello { #[allow(dead_code)] pub fn from_env() -> xflags::Result { Self::from_env_() } #[allow(dead_code)] pub fn from_vec(args: Vec) -> xflags::Result { Self::from_vec_(args) } } // generated end } fn main() { match flags::Hello::from_env() { Ok(flags) => { let bang = if flags.emoji { "❣️" } else { "!" }; println!("Hello {}{}", flags.name, bang); } Err(err) => err.exit(), } } xflags-0.3.1/examples/hello.rs000064400000000000000000000007011046102023000143760ustar 00000000000000mod flags { xflags::xflags! { cmd hello { required name: String optional -e, --emoji } } } fn main() { match flags::Hello::from_env() { Ok(flags) => { let bang = if flags.emoji { "❣️" } else { "!" }; println!("Hello {}{}", flags.name, bang); } Err(err) => { eprintln!("{}", err); std::process::exit(1) } } } xflags-0.3.1/examples/immediate-mode.rs000064400000000000000000000006031046102023000161540ustar 00000000000000use 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 { "" }, ) } xflags-0.3.1/examples/longer.rs000064400000000000000000000043171046102023000145700ustar 00000000000000mod flags { #![allow(unused)] use std::path::PathBuf; xflags::xflags! { src "./examples/longer.rs" cmd rust-analyzer { /// Set verbosity level repeated -v, --verbose /// Log to the specified file instead of stderr. optional --log-file path: PathBuf default cmd run-server { /// Print version optional --version } /// Parse tree cmd parse { /// Suppress printing optional --no-dump } /// Benchmark specific analysis operation cmd analysis-bench { /// Directory with Cargo.toml optional path: PathBuf /// Compute syntax highlighting for this file required --highlight path: PathBuf /// Compute highlighting for this line optional --line num: u32 } } } // generated start // The following code is generated by `xflags` macro. // Run `env UPDATE_XFLAGS=1 cargo build` to regenerate. #[derive(Debug)] pub struct RustAnalyzer { pub verbose: u32, pub log_file: Option, pub subcommand: RustAnalyzerCmd, } #[derive(Debug)] pub enum RustAnalyzerCmd { RunServer(RunServer), Parse(Parse), AnalysisBench(AnalysisBench), } #[derive(Debug)] pub struct RunServer { pub version: bool, } #[derive(Debug)] pub struct Parse { pub no_dump: bool, } #[derive(Debug)] pub struct AnalysisBench { pub path: Option, pub highlight: PathBuf, pub line: Option, } impl RustAnalyzer { #[allow(dead_code)] pub fn from_env() -> xflags::Result { Self::from_env_() } #[allow(dead_code)] pub fn from_vec(args: Vec) -> xflags::Result { Self::from_vec_(args) } } // generated end } fn main() { match flags::RustAnalyzer::from_env() { Ok(flags) => eprintln!("{:#?}", flags), Err(err) => eprintln!("{}", err), } } xflags-0.3.1/examples/non-utf8.rs000064400000000000000000000014731046102023000147600ustar 00000000000000use std::ffi::OsString; mod flags { use std::{ffi::OsString, path::PathBuf}; xflags::xflags! { cmd Cmd { required a: OsString required b: PathBuf required c: String } } } #[cfg(unix)] fn main() { use std::os::unix::ffi::OsStringExt; let flags = flags::Cmd::from_vec(vec![ OsString::from_vec(vec![254].into()), OsString::from_vec(vec![255].into()), "utf8".into(), ]); eprintln!("flags = {:?}", flags); } #[cfg(windows)] fn main() { use std::os::windows::ffi::OsStringExt; let flags = flags::Cmd::from_vec(vec![ OsString::from_wide(&[0xD800]), OsString::from_wide(&[0xDC00]), "utf8".into(), ]); eprintln!("flags = {:?}", flags); } #[cfg(not(any(unix, windows)))] fn main() {} xflags-0.3.1/src/lib.rs000064400000000000000000000251341046102023000130210ustar 00000000000000//! `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](https://github.com/clap-rs/clap) //! * if you want to be maximally minimal, need only basic features (eg, no help //! generation), and want to be pedantically correct, use //! [lexopt](https://github.com/blyxxyz/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: //! //! ```no_run //! 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: //! //! ```text //! ARGS: //! //! File or directory to remove //! //! OPTIONS: //! -r, --recursive //! Remove directories and their contents recursively. //! //! -h, --help //! Prints help information. //! ``` //! //! 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::from_env_() //! } //! pub fn from_vec(args: Vec) -> xflags::Result { //! 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 //! } //! } //! ``` //! //! Nesting **cmd** is allowed. `xflag` automatically generates boilerplate //! enums for subcommands: //! //! ```ignore //! 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::from_env_() //! } //! pub fn from_vec(args: Vec) -> xflags::Result { //! 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() //! } //! } //! //! # fn run_checks(_config: Option, _verbosity: u32) {} //! ``` //! //! 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](https://fuchsia.dev/fuchsia-src/development/api/cli#command_line_arguments) //! 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. use std::fmt; /// Generates a parser for command line arguments from a DSL. /// /// See the module-level for detailed syntax specification. pub use xflags_macros::{parse_or_exit, xflags}; pub type Result = std::result::Result; /// An error occurred when parssing command line arguments. /// /// Either the command line was syntactically invalid, or `--help` was /// explicitly requested. #[derive(Debug)] pub struct Error { msg: String, help: bool, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.msg, f) } } impl std::error::Error for Error {} impl Error { /// Creates a new `Error` from a given message. /// /// Use this to report custom validation errors. pub fn new(message: impl Into) -> Error { Error { msg: message.into(), help: false } } /// Error that carries `--help` message. pub fn is_help(&self) -> bool { self.help } /// Prints the error and exists the process. pub fn exit(self) -> ! { if self.is_help() { println!("{self}"); std::process::exit(0) } else { eprintln!("{self}"); std::process::exit(2) } } } /// Private impl details for macros. #[doc(hidden)] pub mod rt; xflags-0.3.1/src/rt.rs000064400000000000000000000062351046102023000127010ustar 00000000000000use std::{ffi::OsString, fmt, str::FromStr}; use crate::{Error, Result}; macro_rules! format_err { ($($tt:tt)*) => { Error { msg: format!($($tt)*), help: false } }; } macro_rules! bail { ($($tt:tt)*) => { return Err(format_err!($($tt)*)) }; } pub struct Parser { after_double_dash: bool, rargs: Vec, } impl Parser { pub fn new(mut args: Vec) -> Self { args.reverse(); Self { after_double_dash: false, rargs: args } } pub fn new_from_env() -> Self { let args = std::env::args_os().collect::>(); let mut res = Parser::new(args); let _progn = res.next(); res } pub fn pop_flag(&mut self) -> Option> { if self.after_double_dash { self.next().map(Err) } else { let arg = self.next()?; let arg_str = arg.to_str().unwrap_or_default(); if arg_str.starts_with('-') { if arg_str == "--" { self.after_double_dash = true; return self.next().map(Err); } Some(arg.into_string()) } else { Some(Err(arg)) } } } pub fn push_back(&mut self, arg: Result) { let arg = match arg { Ok(it) => it.into(), Err(it) => it, }; self.rargs.push(arg) } fn next(&mut self) -> Option { self.rargs.pop() } pub fn next_value(&mut self, flag: &str) -> Result { self.next().ok_or_else(|| format_err!("expected a value for `{flag}`")) } pub fn next_value_from_str(&mut self, flag: &str) -> Result where T::Err: fmt::Display, { let value = self.next_value(flag)?; self.value_from_str(flag, value) } pub fn value_from_str(&mut self, flag: &str, value: OsString) -> Result where T::Err: fmt::Display, { match value.into_string() { Ok(str) => str.parse::().map_err(|err| format_err!("can't parse `{flag}`, {err}")), Err(it) => { bail!("can't parse `{flag}`, invalid utf8: {it:?}") } } } pub fn unexpected_flag(&self, flag: &str) -> Error { format_err!("unexpected flag: `{flag}`") } pub fn unexpected_arg(&self, arg: OsString) -> Error { format_err!("unexpected argument: {arg:?}") } pub fn subcommand_required(&self) -> Error { format_err!("subcommand is required") } pub fn help(&self, help: &'static str) -> Error { Error { msg: help.to_string(), help: true } } pub fn optional(&self, flag: &str, mut vals: Vec) -> Result> { if vals.len() > 1 { bail!("flag specified more than once: `{flag}`") } Ok(vals.pop()) } pub fn required(&self, flag: &str, mut vals: Vec) -> Result { if vals.len() > 1 { bail!("flag specified more than once: `{flag}`") } vals.pop().ok_or_else(|| format_err!("flag is required: `{flag}`")) } }