From 443841b012a8d795cd5c2bd69ae6e23ef9b16477 Mon Sep 17 00:00:00 2001 From: James McGlashan Date: Tue, 8 Sep 2015 22:53:31 +1000 Subject: [PATCH 1/2] feat: Builder macro to assist with App/Arg/Group/SubCommand building --- examples/18_builder_macro.rs | 82 ++++++++++++++++++++++++ src/macros.rs | 118 +++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 examples/18_builder_macro.rs diff --git a/examples/18_builder_macro.rs b/examples/18_builder_macro.rs new file mode 100644 index 00000000..9f34855c --- /dev/null +++ b/examples/18_builder_macro.rs @@ -0,0 +1,82 @@ +#[macro_use] extern crate clap; + +// No use imports from clap. #[macro_use] gives us `clap_app!` which internally uses `$crate::` + +fn main() { + + // Validation example testing that a file exists + let file_exists = |path| { + if std::fs::metadata(path).is_ok() { + Ok(()) + } else { + Err(String::from("File doesn't exist")) + } + }; + + // External module may contain this subcommand. If this exists in another module, a function is + // required to access it. Recommend `fn clap() -> Clap::SubCommand`. + let external_sub_command = clap_app!( @subcommand foo => + (@arg bar: -b "Bar") + ); + + let matches = clap_app!(MyApp => + (@setting SubcommandRequiredElseHelp) + (version: "1.0") + (author: "Alice") + (about: "Does awesome things") + (@arg config: -c --config #{1, 2} {file_exists} "Sets a custom config file") + (@arg input: * "Input file") + (@group test => + (@attributes +required) + (@arg output: "Sets an optional output file") + (@arg debug: -d ... "Turn debugging information on") + ) + (subcommand: external_sub_command) + (@subcommand test => + (about: "does testing things") + (version: "2.5") + (@arg list: -l "Lists test values") + (@arg test_req: -r requires[list] "Tests requirement for listing") + (@arg aaaa: --aaaa +takes_value { + |a| if a.contains("a") { + Ok(()) + } else { + Err(String::from("string does not contain at least one a")) + } + } "Test if the argument contains an a") + ) + ).get_matches(); + + // You can check the value provided by positional arguments, or option arguments + if let Some(o) = matches.value_of("output") { + println!("Value for output: {}", o); + } + + if let Some(c) = matches.value_of("config") { + println!("Value for config: {}", c); + } + + // You can see how many times a particular flag or argument occurred + // Note, only flags can have multiple occurrences + match matches.occurrences_of("debug") { + 0 => println!("Debug mode is off"), + 1 => println!("Debug mode is kind of on"), + 2 => println!("Debug mode is on"), + 3 | _ => println!("Don't be crazy"), + } + + // You can check for the existence of subcommands, and if found use their + // matches just as you would the top level app + if let Some(ref matches) = matches.subcommand_matches("test") { + // "$ myapp test" was run + if matches.is_present("list") { + // "$ myapp test -l" was run + println!("Printing testing lists..."); + } else { + println!("Not printing testing lists..."); + } + } + + + // Continued program logic goes here... +} diff --git a/src/macros.rs b/src/macros.rs index 4e383eb6..ef007d1e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -739,3 +739,121 @@ macro_rules! crate_version { option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")) } } + +/// App, Arg, SubCommand and Group builder macro (Usage-string like input) +#[macro_export] +macro_rules! clap_app { + (@app ($builder:expr)) => { $builder }; + (@app ($builder:expr) (@arg $name:ident: $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + ($builder.arg(clap_app!{ @arg ($crate::Arg::with_name(stringify!($name))) (-) $($tail)* })) + $($tt)* + } + }; + (@app ($builder:expr) (@setting $setting:ident) $($tt:tt)*) => { + clap_app!{ @app + ($builder.setting($crate::AppSettings::$setting)) + $($tt)* + } + }; + // Treat the application builder as an argument to set it's attributes + (@app ($builder:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => { + clap_app!{ @app (clap_app!{ @arg ($builder) $($attr)* }) $($tt)* } + }; + (@app ($builder:expr) (@group $name:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + (clap_app!{ @group ($builder, $crate::ArgGroup::with_name(stringify!($name))) $($tail)* }) + $($tt)* + } + }; + // Handle subcommand creation + (@app ($builder:expr) (@subcommand $name:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + ($builder.subcommand( + clap_app!{ @app ($crate::SubCommand::with_name(stringify!($name))) $($tail)* } + )) + $($tt)* + } + }; + // Yaml like function calls - used for setting varous meta directly against the app + (@app ($builder:expr) ($ident:ident: $($v:expr),*) $($tt:tt)*) => { + clap_app!{ @app ($builder.$ident($($v),*)) $($tt)* } + }; + + // Add members to group and continue argument handling with the parent builder + (@group ($builder:expr, $group:expr)) => { $builder.arg_group($group) }; + (@group ($builder:expr, $group:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => { + clap_app!{ @group ($builder, clap_app!{ @arg ($group) (-) $($attr)* }) $($tt)* } + }; + (@group ($builder:expr, $group:expr) (@arg $name:ident: $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @group + (clap_app!{ @app ($builder) (@arg $name: $($tail)*) }, + $group.add(stringify!($name))) + $($tt)* + } + }; + + // No more tokens to munch + (@arg ($arg:expr) $modes:tt) => { $arg }; + // Shorthand tokens influenced by the usage_string + (@arg ($arg:expr) $modes:tt --$long:ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.long(stringify!($long))) $modes $($tail)* } + }; + (@arg ($arg:expr) $modes:tt -$short:ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.short(stringify!($short))) $modes $($tail)* } + }; + (@arg ($arg:expr) (-) <$var:ident> $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) (+) +takes_value +required $($tail)* } + }; + (@arg ($arg:expr) (+) <$var:ident> $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) (+) $($tail)* } + }; + (@arg ($arg:expr) (-) [$var:ident] $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) +takes_value (+) $($tail)* } + }; + (@arg ($arg:expr) (+) [$var:ident] $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) (+) $($tail)* } + }; + (@arg ($arg:expr) $modes:tt ... $($tail:tt)*) => { + clap_app!{ @arg ($arg) $modes +multiple $($tail)* } + }; + // Shorthand magic + (@arg ($arg:expr) $modes:tt #{$n:expr, $m:expr} $($tail:tt)*) => { + clap_app!{ @arg ($arg) $modes min_values($n) max_values($m) $($tail)* } + }; + (@arg ($arg:expr) $modes:tt * $($tail:tt)*) => { + clap_app!{ @arg ($arg) $modes +required $($tail)* } + }; + // !foo -> .foo(false) + (@arg ($arg:expr) $modes:tt !$ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.$ident(false)) $modes $($tail)* } + }; + // foo -> .foo(true) + (@arg ($arg:expr) $modes:tt +$ident:ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.$ident(true)) $modes $($tail)* } + }; + // Validator + (@arg ($arg:expr) $modes:tt {$fn_:expr} $($tail:tt)*) => { + clap_app!{ @arg ($arg.validator($fn_)) $modes $($tail)* } + }; + (@as_expr $expr:expr) => { $expr }; + // 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 + (@arg ($arg:expr) $modes:tt $ident:ident[$($target:ident)*] $($tail:tt)*) => { + clap_app!{ @arg ($arg $( .$ident(stringify!($target)) )*) $modes $($tail)* } + }; + // Inherit builder's functions + (@arg ($arg:expr) $modes:tt $ident:ident($($expr:expr)*) $($tail:tt)*) => { + clap_app!{ @arg ($arg.$ident($($expr)*)) $modes $($tail)* } + }; + + // Build a subcommand outside of an app. + (@subcommand $name:ident => $($tail:tt)*) => { + clap_app!{ @app ($crate::SubCommand::with_name(stringify!($name))) $($tail)* } + }; + // Start the magic + ($name:ident => $($tail:tt)*) => {{ + clap_app!{ @app ($crate::App::new(stringify!($name))) $($tail)*} + }}; +} From a99e96d2240f8496e632e3856385c68431b34f94 Mon Sep 17 00:00:00 2001 From: James McGlashan Date: Wed, 9 Sep 2015 12:28:16 +1000 Subject: [PATCH 2/2] New contributor! --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 48567bfa..201411dd 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,6 +4,7 @@ The following is a list of contributors to the [clap](https://github.com/kbknapp * [Ivan Dmitrievsky](https://github.com/idmit) <> * [J/A](https://github.com/archer884) * [Jacob Helwig](https://github.com/jhelwig) <> + * [James McGlashan](https://github.com/james-darkfox) * [Kevin K.](https://github.com/kbknapp) <> * [Markus Unterwaditzer](https://github.com/untitaker) <> * [Sebastian Thiel](https://github.com/Byron) <>