mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
feat: Usage parser macro
This is intended to replace the runtime usage parser and is not meant to be a complete API in of itself, like `clap_app!`. What is in scope is everything that visually makes sense as in a usage string (see [docopt](http://docopt.org/) for inspiration). General setting of attributes is out of scope. This deviates from both `clap_app` and the runtime usage parser - `clap_app` supported multiple values but has a bug because we made `Arg::value_name` non-appending, so we aren't supporting this yet - We do not yet support optional flags that take a value - In both, `...` is multiple occurrences and values while its only multiple occurrences for us - We explicitly support optional values for flags - Unlike `clap_app`, our name is optional - Unlike runtime usage parser, our name syntax is simpler - Unlike runtime usage parser, our name syntax does not allow modifiers Its more limited than I would like. Hopefully some people better with macros can expand the feature set and turn more runtime errors into compile-time errors. This is to prepare for deprecating the runtime usage parser (#8).
This commit is contained in:
parent
136c8e229d
commit
263fe30568
2 changed files with 476 additions and 0 deletions
294
src/macros.rs
294
src/macros.rs
|
@ -175,6 +175,300 @@ macro_rules! app_from_crate {
|
|||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! arg_impl {
|
||||
( @string $val:ident ) => {
|
||||
stringify!($val)
|
||||
};
|
||||
( @string $val:literal ) => {{
|
||||
let ident_or_string_literal: &str = $val;
|
||||
ident_or_string_literal
|
||||
}};
|
||||
( @string $val:tt ) => {
|
||||
::std::compile_error!("Only identifiers or string literals supported");
|
||||
};
|
||||
( @string ) => {
|
||||
None
|
||||
};
|
||||
|
||||
( @char $val:ident ) => {{
|
||||
let ident_or_char_literal = stringify!($val);
|
||||
debug_assert_eq!(
|
||||
ident_or_char_literal.len(),
|
||||
1,
|
||||
"Single-letter identifier expected, got {}",
|
||||
ident_or_char_literal
|
||||
);
|
||||
ident_or_char_literal.chars().next().unwrap()
|
||||
}};
|
||||
( @char $val:literal ) => {{
|
||||
let ident_or_char_literal: char = $val;
|
||||
ident_or_char_literal
|
||||
}};
|
||||
( @char ) => {{
|
||||
None
|
||||
}};
|
||||
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
--$long:ident
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
$crate::arg_impl! {
|
||||
@arg
|
||||
({
|
||||
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
|
||||
debug_assert!(!$arg.is_set($crate::ArgSettings::MultipleOccurrences), "Flags should precede `...`");
|
||||
|
||||
let mut arg = $arg;
|
||||
let long = $crate::arg_impl! { @string $long };
|
||||
if arg.get_name().is_empty() {
|
||||
arg = arg.name(long);
|
||||
}
|
||||
arg.long(long)
|
||||
})
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
--$long:literal
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
$crate::arg_impl! {
|
||||
@arg
|
||||
({
|
||||
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
|
||||
debug_assert!(!$arg.is_set($crate::ArgSettings::MultipleOccurrences), "Flags should precede `...`");
|
||||
|
||||
let mut arg = $arg;
|
||||
let long = $crate::arg_impl! { @string $long };
|
||||
if arg.get_name().is_empty() {
|
||||
arg = arg.name(long);
|
||||
}
|
||||
arg.long(long)
|
||||
})
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
-$short:ident
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
$crate::arg_impl! {
|
||||
@arg
|
||||
({
|
||||
debug_assert_eq!($arg.get_long(), None, "Short flags should precede long flags");
|
||||
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
|
||||
debug_assert!(!$arg.is_set($crate::ArgSettings::MultipleOccurrences), "Flags should precede `...`");
|
||||
|
||||
$arg.short($crate::arg_impl! { @char $short })
|
||||
})
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
-$short:literal
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
$crate::arg_impl! {
|
||||
@arg
|
||||
({
|
||||
debug_assert_eq!($arg.get_long(), None, "Short flags should precede long flags");
|
||||
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
|
||||
debug_assert!(!$arg.is_set($crate::ArgSettings::MultipleOccurrences), "Flags should precede `...`");
|
||||
|
||||
$arg.short($crate::arg_impl! { @char $short })
|
||||
})
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
<$value_name:ident>
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
$crate::arg_impl! {
|
||||
@arg
|
||||
({
|
||||
debug_assert!(!$arg.is_set($crate::ArgSettings::MultipleOccurrences), "Values should precede `...`");
|
||||
debug_assert_eq!($arg.get_value_names(), None, "Multiple values not yet supported");
|
||||
|
||||
let mut arg = $arg;
|
||||
|
||||
arg = arg.required(true);
|
||||
arg = arg.takes_value(true);
|
||||
|
||||
let value_name = $crate::arg_impl! { @string $value_name };
|
||||
if arg.get_name().is_empty() {
|
||||
arg = arg.name(value_name);
|
||||
}
|
||||
arg.value_name(value_name)
|
||||
})
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
[$value_name:ident]
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
$crate::arg_impl! {
|
||||
@arg
|
||||
({
|
||||
debug_assert!(!$arg.is_set($crate::ArgSettings::MultipleOccurrences), "Values should precede `...`");
|
||||
debug_assert_eq!($arg.get_value_names(), None, "Multiple values not yet supported");
|
||||
|
||||
let mut arg = $arg;
|
||||
|
||||
if arg.get_long().is_none() && arg.get_short().is_none() {
|
||||
arg = arg.required(false);
|
||||
} else {
|
||||
arg = arg.min_values(0).max_values(1);
|
||||
}
|
||||
arg = arg.takes_value(true);
|
||||
|
||||
let value_name = $crate::arg_impl! { @string $value_name };
|
||||
if arg.get_name().is_empty() {
|
||||
arg = arg.name(value_name);
|
||||
}
|
||||
arg.value_name(value_name)
|
||||
})
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
...
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
$crate::arg_impl! {
|
||||
@arg
|
||||
({
|
||||
$arg.multiple_occurrences(true)
|
||||
})
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
$help:literal
|
||||
) => {
|
||||
$arg.help($help)
|
||||
};
|
||||
(
|
||||
@arg
|
||||
($arg:expr)
|
||||
) => {
|
||||
$arg
|
||||
};
|
||||
}
|
||||
|
||||
/// Create an [`Arg`] from a usage string.
|
||||
///
|
||||
/// Allows creation of basic settings for the [`Arg`].
|
||||
///
|
||||
/// **NOTE**: Not all settings may be set using the usage string method. Some properties are
|
||||
/// only available via the builder pattern.
|
||||
///
|
||||
/// # Syntax
|
||||
///
|
||||
/// Usage strings typically following the form:
|
||||
///
|
||||
/// ```notrust
|
||||
/// [explicit name] [short] [long] [value names] [...] [help string]
|
||||
/// ```
|
||||
///
|
||||
/// ### Explicit Name
|
||||
///
|
||||
/// The name may be either a bare-word or a string, followed by a `:`, like `name:` or
|
||||
/// `"name":`.
|
||||
///
|
||||
/// *Note:* This is an optional field, if it's omitted the argument will use one of the additional
|
||||
/// fields as the name using the following priority order:
|
||||
///
|
||||
/// 1. Explicit Name
|
||||
/// 2. Long
|
||||
/// 3. Value Name
|
||||
///
|
||||
/// See [`Arg::name`][clap::Arg::name].
|
||||
///
|
||||
/// ### Short
|
||||
///
|
||||
/// A short flag is a `-` followed by either a bare-character or quoted character, like `-f` or
|
||||
/// `-'f'`.
|
||||
///
|
||||
/// See [`Arg::short`][clap::Arg::sort].
|
||||
///
|
||||
/// ### Long
|
||||
///
|
||||
/// A long flag is a `--` followed by either a bare-word or a string, like `--foo` or
|
||||
/// `--"foo"`.
|
||||
///
|
||||
/// See [`Arg::long`][clap::Arg::long].
|
||||
///
|
||||
/// ### Values (Value Notation)
|
||||
///
|
||||
/// This is set by placing bare-word between:
|
||||
/// - `[]` like `[FOO]`
|
||||
/// - Positional argument: optional
|
||||
/// - Named argument: optional value
|
||||
/// - `<>` like `<FOO>`: required
|
||||
///
|
||||
/// See [`Arg::value_name`][clap::Arg::value_name].
|
||||
///
|
||||
/// ### `...`
|
||||
///
|
||||
/// `...` (three consecutive dots/periods) specifies that this argument may occur multiple
|
||||
/// times (not to be confused with multiple values per occurrence).
|
||||
///
|
||||
/// See [`Arg::multiple_occurrences`][clap::Arg::multiple_occurrences].
|
||||
///
|
||||
/// ### Help String
|
||||
///
|
||||
/// The help string is denoted between a pair of single quotes `''` and may contain any
|
||||
/// characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, arg};
|
||||
/// App::new("prog")
|
||||
/// .args(&[
|
||||
/// arg!(--config <FILE> "a required file for the configuration and no short"),
|
||||
/// arg!(-d --debug ... "turns on debugging information and allows multiples"),
|
||||
/// arg!([input] "an optional input file to use")
|
||||
/// ])
|
||||
/// # ;
|
||||
/// ```
|
||||
/// [`Arg`]: ./struct.Arg.html
|
||||
#[macro_export]
|
||||
macro_rules! arg {
|
||||
( $name:ident: $($tail:tt)+ ) => {
|
||||
$crate::arg_impl! {
|
||||
@arg ($crate::Arg::new($crate::arg_impl! { @string $name })) $($tail)+
|
||||
}
|
||||
};
|
||||
( $($tail:tt)+ ) => {{
|
||||
let arg = $crate::arg_impl! {
|
||||
@arg ($crate::Arg::default()) $($tail)+
|
||||
};
|
||||
debug_assert!(!arg.get_name().is_empty(), "Without a value or long flag, the `name:` prefix is required");
|
||||
arg
|
||||
}};
|
||||
}
|
||||
|
||||
/// Deprecated, see `clap::Parser` for a declarative API (Issue clap-rs/clap#2835)
|
||||
#[deprecated(
|
||||
since = "3.0.0",
|
||||
|
|
182
tests/macros.rs
182
tests/macros.rs
|
@ -517,3 +517,185 @@ fn validator() {
|
|||
assert_eq!(matches.value_of_t::<u16>("func1").ok(), Some(34));
|
||||
assert_eq!(matches.value_of_t::<u16>("func2").ok(), Some(56));
|
||||
}
|
||||
|
||||
mod arg {
|
||||
#[test]
|
||||
fn name_explicit() {
|
||||
let arg = clap::arg!(foo: --bar <NUM>);
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_long(), Some("bar"));
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_from_long() {
|
||||
let arg = clap::arg!(--bar <NUM>);
|
||||
assert_eq!(arg.get_name(), "bar");
|
||||
assert_eq!(arg.get_long(), Some("bar"));
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_from_value() {
|
||||
let arg = clap::arg!(<NUM>);
|
||||
assert_eq!(arg.get_name(), "NUM");
|
||||
assert_eq!(arg.get_long(), None);
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn name_none_fails() {
|
||||
clap::arg!("Help");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn short_only_fails() {
|
||||
clap::arg!(-b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short() {
|
||||
let arg = clap::arg!(foo: -b);
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(foo: -'b');
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b ...);
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b "How to use it");
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), Some("How to use it"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_and_long() {
|
||||
let arg = clap::arg!(foo: -b --hello);
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_long(), Some("hello"));
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(foo: -'b' --hello);
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_long(), Some("hello"));
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b --hello ...);
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_long(), Some("hello"));
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b --hello "How to use it");
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_long(), Some("hello"));
|
||||
assert_eq!(arg.get_short(), Some('b'));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), Some("How to use it"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positional() {
|
||||
let arg = clap::arg!(<NUM>);
|
||||
assert_eq!(arg.get_name(), "NUM");
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!([NUM]);
|
||||
assert_eq!(arg.get_name(), "NUM");
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(!arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(<NUM>);
|
||||
assert_eq!(arg.get_name(), "NUM");
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(foo: <NUM>);
|
||||
assert_eq!(arg.get_name(), "foo");
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(<NUM> ...);
|
||||
assert_eq!(arg.get_name(), "NUM");
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), None);
|
||||
|
||||
let arg = clap::arg!(<NUM> "How to use it");
|
||||
assert_eq!(arg.get_name(), "NUM");
|
||||
assert_eq!(arg.get_value_names(), Some(vec!["NUM"].as_slice()));
|
||||
assert!(!arg.is_set(clap::ArgSettings::MultipleOccurrences));
|
||||
assert!(arg.is_set(clap::ArgSettings::Required));
|
||||
assert_eq!(arg.get_help(), Some("How to use it"));
|
||||
}
|
||||
}
|
||||
|
||||
mod arg_impl {
|
||||
#[test]
|
||||
fn string_ident() {
|
||||
let expected = "one";
|
||||
let actual = clap::arg_impl! { @string one };
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_literal() {
|
||||
let expected = "one";
|
||||
let actual = clap::arg_impl! { @string "one" };
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_ident() {
|
||||
let expected = 'o';
|
||||
let actual = clap::arg_impl! { @char o };
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_literal() {
|
||||
let expected = 'o';
|
||||
let actual = clap::arg_impl! { @char 'o' };
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue