Allow literals in builder macros

This commit is contained in:
Pavan Kumar Sunkara 2020-04-12 12:00:10 +02:00
parent ee18ca44f1
commit da32adeb0e
4 changed files with 136 additions and 43 deletions

View file

@ -7,13 +7,10 @@ assignees: ''
---
<!--
Please use the following template to assist with creating an issue and to ensure a speedy resolution. If an area is not applicable, feel free to delete the area or mark with `N/A`
-->
### Make sure you completed the following tasks
### Rust Version
Use the output of `rustc -V`
- [ ] Searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [ ] Searched the closes issues
### Code
@ -28,9 +25,10 @@ Use the output of `rustc -V`
2. ???
3. PROFIT!!!
### Affected Version of `clap*`
### Version
Can be found in Cargo.lock or Cargo.toml of your project (i.e. `grep clap Cargo.lock`). PLEASE DO NOT PUT "latest" HERE, use precise version. Put `master` (or other branch) if you're using the repo directly.
* **Rust**: Output of `rustc -V`
* **Clap**: Can be found in Cargo.lock or Cargo.toml of your project (i.e. `grep clap Cargo.lock`). PLEASE DO NOT PUT "latest" HERE, use precise version. Put `master` (or other branch) if you're using the repo directly.
### Actual Behavior Summary

View file

@ -10,7 +10,7 @@ assignees: ''
### Make sure you completed the following tasks
- [ ] Searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [ ] Searched the closes issues
- [ ] Searched the closed issues
### Describe your use case
@ -29,4 +29,5 @@ Please explain what the wanted solution should look like. You are **strongly enc
A clear and concise description of any alternative solutions or features you've managed to come up with.
### Additional context
Add any other context about the feature request here.

View file

@ -589,7 +589,7 @@ macro_rules! app_from_crate {
};
}
/// Build `App`, `Arg`s, ``s and `Group`s with Usage-string like input
/// Build `App`, `Arg` and `Group` with Usage-string like input
/// but without the associated parsing runtime cost.
///
/// `clap_app!` also supports several shorthand syntaxes.
@ -621,12 +621,11 @@ macro_rules! app_from_crate {
/// );
/// # }
/// ```
///
/// # Shorthand Syntax for Args
///
/// * A single hyphen followed by a character (such as `-c`) sets the [`Arg::short`]
/// * A double hyphen followed by a character or word (such as `--config`) sets [`Arg::long`]
/// * If one wishes to use a [`Arg::long`] with a hyphen inside (i.e. `--config-file`), you
/// must use `--("config-file")` due to limitations of the Rust macro system.
/// * Three dots (`...`) sets [`Arg::multiple(true)`]
/// * Angled brackets after either a short or long will set [`Arg::value_name`] and
/// `Arg::required(true)` such as `--config <FILE>` = `Arg::value_name("FILE")` and
@ -661,6 +660,10 @@ macro_rules! app_from_crate {
/// * `(@arg something: --something)` could also be `(@arg ("something-else"): --("something-else"))`
/// * `(@subcommand something => ...)` could also be `(@subcommand ("something-else") => ...)`
///
/// Or it can be even simpler by using the literal directly
/// * `(@arg "something-else": --"something-else")`
/// * `(@subcommand "something-else" => ...)`
///
/// [`Arg::short`]: ./struct.Arg.html#method.short
/// [`Arg::long`]: ./struct.Arg.html#method.long
/// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple
@ -679,6 +682,13 @@ macro_rules! clap_app {
$($tt)*
}
};
(@app ($builder:expr) (@arg $name:literal: $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @app
($builder.arg(
$crate::clap_app!{ @arg ($crate::Arg::with_name(stringify!($name).trim_matches('"'))) (-) $($tail)* }))
$($tt)*
}
};
(@app ($builder:expr) (@arg $name:ident: $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @app
($builder.arg(
@ -686,16 +696,18 @@ macro_rules! clap_app {
$($tt)*
}
};
// Settings
(@app ($builder:expr) (@setting $setting:ident) $($tt:tt)*) => {
$crate::clap_app!{ @app
($builder.setting($crate::AppSettings::$setting))
$($tt)*
}
};
// Treat the application builder as an argument to set its attributes
// Treat the application builder as an argument to set its attributes
(@app ($builder:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @app ($crate::clap_app!{ @arg ($builder) $($attr)* }) $($tt)* }
};
// ArgGroup
(@app ($builder:expr) (@group $name:ident => $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @app
($crate::clap_app!{ @group ($builder, $crate::ArgGroup::with_name(stringify!($name))) $($tail)* })
@ -714,7 +726,23 @@ macro_rules! clap_app {
$($tt)*
}
};
// Handle subcommand creation
// Handle subcommand creation
(@app ($builder:expr) (@subcommand ($name:expr) => $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @app
($builder.subcommand(
$crate::clap_app!{ @app ($crate::App::new($name)) $($tail)* }
))
$($tt)*
}
};
(@app ($builder:expr) (@subcommand $name:literal => $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @app
($builder.subcommand(
$crate::clap_app!{ @app ($crate::App::new(stringify!($name).trim_matches('"'))) $($tail)* }
))
$($tt)*
}
};
(@app ($builder:expr) (@subcommand $name:ident => $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @app
($builder.subcommand(
@ -723,29 +751,33 @@ macro_rules! clap_app {
$($tt)*
}
};
(@app ($builder:expr) (@subcommand ($name:expr) => $($tail:tt)*) $($tt:tt)*) => {
clap_app!{ @app
($builder.subcommand(
$crate::clap_app!{ @app ($crate::App::new($name)) $($tail)* }
))
$($tt)*
}
};
// Yaml like function calls - used for setting various meta directly against the app
// Yaml like function calls - used for setting various meta directly against the app
(@app ($builder:expr) ($ident:ident: $($v:expr),*) $($tt:tt)*) => {
// clap_app!{ @app ($builder.$ident($($v),*)) $($tt)* }
$crate::clap_app!{ @app
($builder.$ident($($v),*))
$($tt)*
}
};
// Add members to group and continue argument handling with the parent builder
// Add members to group and continue argument handling with the parent builder
(@group ($builder:expr, $group:expr)) => { $builder.group($group) };
// Treat the group builder as an argument to set its attributes
(@group ($builder:expr, $group:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @group ($builder, $crate::clap_app!{ @arg ($group) (-) $($attr)* }) $($tt)* }
};
(@group ($builder:expr, $group:expr) (@arg ($name:expr): $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @group
($crate::clap_app!{ @app ($builder) (@arg ($name): $($tail)*) },
$group.arg($name))
$($tt)*
}
};
(@group ($builder:expr, $group:expr) (@arg $name:literal: $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @group
($crate::clap_app!{ @app ($builder) (@arg $name: $($tail)*) },
$group.arg(stringify!($name).trim_matches('"')))
$($tt)*
}
};
(@group ($builder:expr, $group:expr) (@arg $name:ident: $($tail:tt)*) $($tt:tt)*) => {
$crate::clap_app!{ @group
($crate::clap_app!{ @app ($builder) (@arg $name: $($tail)*) },
@ -753,16 +785,24 @@ macro_rules! clap_app {
$($tt)*
}
};
// No more tokens to munch
// No more tokens to munch
(@arg ($arg:expr) $modes:tt) => { $arg };
// Shorthand tokens influenced by the usage_string
// Shorthand tokens influenced by the usage_string
(@arg ($arg:expr) $modes:tt --($long:expr) $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.long($long)) $modes $($tail)* }
};
(@arg ($arg:expr) $modes:tt --$long:literal $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.long(stringify!($long).trim_matches('"'))) $modes $($tail)* }
};
(@arg ($arg:expr) $modes:tt --$long:ident $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.long(stringify!($long))) $modes $($tail)* }
};
(@arg ($arg:expr) $modes:tt -($short:expr) $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.short($short)) $modes $($tail)* }
};
(@arg ($arg:expr) $modes:tt -$short:literal $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.short($short.to_string().chars().next().expect(r#""" is not allowed here"#))) $modes $($tail)* }
};
(@arg ($arg:expr) $modes:tt -$short:ident $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.short(stringify!($short).chars().next().unwrap())) $modes $($tail)* }
};
@ -781,49 +821,56 @@ macro_rules! clap_app {
(@arg ($arg:expr) $modes:tt ... $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg) $modes +multiple $($tail)* }
};
// Shorthand magic
// Shorthand magic
(@arg ($arg:expr) $modes:tt #{$n:expr, $m:expr} $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg) $modes min_values($n) max_values($m) $($tail)* }
};
(@arg ($arg:expr) $modes:tt * $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg) $modes +required $($tail)* }
};
// !foo -> .foo(false)
// !foo -> .foo(false)
(@arg ($arg:expr) $modes:tt !$ident:ident $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.$ident(false)) $modes $($tail)* }
};
// +foo -> .foo(true)
// +foo -> .foo(true)
(@arg ($arg:expr) $modes:tt +$ident:ident $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.$ident(true)) $modes $($tail)* }
};
// Validator
// Validator
(@arg ($arg:expr) $modes:tt {$fn_:expr} $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.validator($fn_)) $modes $($tail)* }
};
(@as_expr $expr:expr) => { $expr };
// Help
// Help
(@arg ($arg:expr) $modes:tt $desc:tt) => { $arg.help(clap_app!{ @as_expr $desc }) };
// Handle functions that need to be called multiple times for each argument
// Handle functions that need to be called multiple times for each argument
(@arg ($arg:expr) $modes:tt $ident:ident[$($target:literal)*] $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg $( .$ident(stringify!($target).trim_matches('"')) )*) $modes $($tail)* }
};
(@arg ($arg:expr) $modes:tt $ident:ident[$($target:ident)*] $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg $( .$ident(stringify!($target)) )*) $modes $($tail)* }
};
// Inherit builder's functions
// Inherit builder's functions
(@arg ($arg:expr) $modes:tt $ident:ident($($expr:expr)*) $($tail:tt)*) => {
$crate::clap_app!{ @arg ($arg.$ident($($expr)*)) $modes $($tail)* }
};
// Build a subcommand outside of an app.
(@subcommand $name:ident => $($tail:tt)*) => {
$crate::clap_app!{ @app ($crate::App::new(stringify!($name))) $($tail)* }
};
// Build a subcommand outside of an app.
(@subcommand ($name:expr) => $($tail:tt)*) => {
$crate::clap_app!{ @app ($crate::App::new($name)) $($tail)* }
};
// Start the magic
(@subcommand $name:literal => $($tail:tt)*) => {
$crate::clap_app!{ @app ($crate::App::new(stringify!($name).trim_matches('"'))) $($tail)* }
};
(@subcommand $name:ident => $($tail:tt)*) => {
$crate::clap_app!{ @app ($crate::App::new(stringify!($name))) $($tail)* }
};
// Start the magic
(($name:expr) => $($tail:tt)*) => {{
$crate::clap_app!{ @app ($crate::App::new($name)) $($tail)*}
}};
($name:literal => $($tail:tt)*) => {{
$crate::clap_app!{ @app ($crate::App::new(stringify!($name).trim_matches('"'))) $($tail)*}
}};
($name:ident => $($tail:tt)*) => {{
$crate::clap_app!{ @app ($crate::App::new(stringify!($name))) $($tail)*}
}};

View file

@ -1,5 +1,27 @@
mod utils;
use clap::{arg_enum, clap_app, ErrorKind};
static LITERALS: &str = "clap-tests 0.1
USAGE:
clap-tests [FLAGS] [OPTIONS] [SUBCOMMAND]
FLAGS:
-4, --4 Sets priority to 4
-5, --5 Sets priority to 5
-6, --6 Sets priority to 6
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-t, --task-num <task-num> Task number [possible values: all, 0, 1, 2]
SUBCOMMANDS:
0 Set everything to zero priority
help Prints this message or the help of the given subcommand(s)
view-tasks View all tasks";
#[test]
fn basic() {
clap_app!(claptests =>
@ -284,6 +306,31 @@ fn group_macro_set_not_required() {
assert!(!matches.is_present("difficulty"));
}
#[test]
fn literals() {
let app = clap_app!("clap-tests" =>
(version: "0.1")
(@arg "task-num": -"t-n" --"task-num" +takes_value possible_value["all" 0 1 2]
"Task number")
(@group priority =>
(@arg "4": -4 --4 "Sets priority to 4")
(@arg ("5"): -('5') --5 "Sets priority to 5")
(@arg 6: -6 --6 "Sets priority to 6")
)
(@subcommand "view-tasks" =>
(about: "View all tasks"))
(@subcommand 0 =>
(about: "Set everything to zero priority"))
);
assert!(utils::compare_output(
app,
"clap-tests --help",
LITERALS,
false
));
}
#[test]
fn arg_enum() {
// Helper macros to avoid repetition