From 2c499f8015a199827cdf1fa3ec4f6f171722f8c7 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 17 Apr 2015 10:20:56 -0400 Subject: [PATCH] feat(macros): add ability to create enums pub or priv with derives Err type of FromStr trait changed from &'a str->String in order to allow showing valid values on failed parse Breaking Change --- examples/13a_EnumValuesAutomatic.rs | 51 +++++++--- src/macros.rs | 141 ++++++++++++++++++++++++++-- 2 files changed, 170 insertions(+), 22 deletions(-) diff --git a/examples/13a_EnumValuesAutomatic.rs b/examples/13a_EnumValuesAutomatic.rs index d719e7691cb..f4cb9417926 100644 --- a/examples/13a_EnumValuesAutomatic.rs +++ b/examples/13a_EnumValuesAutomatic.rs @@ -1,12 +1,13 @@ // You can use clap's value_t! macro with a custom enum by implementing the std::str::FromStr -// trait which is very straight forward. There are two ways to do this, for simple enums you -// can use clap's simple_enum! macro, but if you require additional functionality you can -// create and implement the trait manually. +// trait which is very straight forward. There are three ways to do this, for simple enums +// meaning those that don't require 'pub' or any '#[derive()]' directives you can use clas's +// simple_enum! macro. For those that require 'pub' or any '#[derive()]'s you can use clap's +// arg_enum! macro. The third way is to implement std::str::FromStr manually. // -// In the following example we will create an enum with 4 values, assign a positional argument -// that accepts only one of those values, and use clap to parse the argument. +// In most circumstances using either simple_enum! or arg_enum! is fine. // -// Start with bringing the trait into scope. +// In the following example we will create two enums using macros, assign a positional argument +// that accepts only one of those values, and use clap to parse the argument. // Add clap like normal #[macro_use] @@ -16,24 +17,44 @@ use clap::{App, Arg}; // Define your enum, the simple_num! macro takes a enum name followed by => and each value // separated by a ',' -simple_enum!{ Vals => Foo, Bar, Baz, Qux } +simple_enum!{ Foo => Bar, Baz, Qux } + +// Using arg_enum! is more like traditional enum declarations +// +// **NOTE:** Only bare variants are supported +arg_enum!{ + #[derive(Debug)] + pub enum Oof { + Rab, + Zab, + Xuq + } +} fn main() { // Create the application like normal let m = App::new("myapp") // Use a single positional argument that is required - .arg(Arg::from_usage(" 'The type to use'") - // Define the list of possible values - .possible_values(vec!["Foo", "Bar", "Baz", "Qux"])) + .arg(Arg::from_usage(" 'The Foo to use'") + // You can define a list of possible values if you want the values to be + // displayed in the help information. Whether you use possible_values() or + // not, the valid values will ALWAYS be displayed on a failed parse. + .possible_values(vec!["Bar", "Baz", "Qux"])) + // For the second positional, lets not use possible_values() just to show the difference + .arg_from_usage(" 'The Oof to use'") .get_matches(); - let t = value_t_or_exit!(m.value_of("type"), Vals); + let t = value_t_or_exit!(m.value_of("type"), Foo); + let t2 = value_t_or_exit!(m.value_of("type2"), Oof); + // Now we can use our enum like normal. match t { - Vals::Foo => println!("Found a Foo"), - Vals::Bar => println!("Found a Bar"), - Vals::Baz => println!("Found a Baz"), - Vals::Qux => println!("Found a Qux") + Foo::Bar => println!("Found a Bar"), + Foo::Baz => println!("Found a Baz"), + Foo::Qux => println!("Found a Qux") } + + // Since our Oof derives Debug, we can do this: + println!("Oof: {:?}", t2); } \ No newline at end of file diff --git a/src/macros.rs b/src/macros.rs index e612ed3a997..ccf0d77af6e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -101,8 +101,8 @@ macro_rules! value_t { for pv in v { match pv.parse::<$t>() { Ok(rv) => tmp.push(rv), - Err(_) => { - err = Some(format!("{} isn't a valid {}",pv,stringify!($t))); + Err(e) => { + err = Some(format!("{} isn't a valid {}\n{}",pv,stringify!($t),e)); break } } @@ -169,10 +169,11 @@ macro_rules! value_t_or_exit { Some(v) => { match v.parse::<$t>() { Ok(val) => val, - Err(_) => { - println!("{} isn't a valid {}\n{}\nPlease re-run with --help for more information", + Err(e) => { + println!("{} isn't a valid {}\n{}\n{}\nPlease re-run with --help for more information", v, stringify!($t), + e, $m.usage()); ::std::process::exit(1); } @@ -243,13 +244,139 @@ macro_rules! simple_enum { $($v),+ } - impl<'a> std::str::FromStr for $e { - type Err = &'a str; + impl std::str::FromStr for $e { + type Err = String; fn from_str(s: &str) -> Result::Err> { match s { $(stringify!($v) => Ok($e::$v),)+ - _ => Err("no match") + _ => Err({ + let v = vec![ + $(stringify!($v),)+ + ]; + format!("valid:{}", v.iter().fold(String::new(),|a, i| a + &format!(" {}", i)[..])) + }) + } + } + } + }; +} + +/// Convenience macro to generate more complete enums with variants to be used as a type when parsing +/// arguments. +/// +/// These enums support pub (or not) and use of the #[derive()] traits +/// +/// +/// # Example +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::{App, Arg}; +/// arg_enum!{ +/// #[derive(Debug)] +/// pub enum Foo { +/// Bar, +/// Baz, +/// Qux +/// } +/// } +/// // Foo enum can now be used via Foo::Bar, or Foo::Baz, etc +/// // and implements std::str::FromStr to use with the value_t! macros +/// fn main() { +/// let m = App::new("app") +/// .arg_from_usage(" 'the foo'") +/// .get_matches(); +/// let f = value_t_or_exit!(m.value_of("foo"), Foo); +/// +/// // Use f like any other Foo variant... +/// } +/// ``` +#[macro_export] +macro_rules! arg_enum { + (enum $e:ident { $($v:ident),+ } ) => { + enum $e { + $($v),+ + } + + impl std::str::FromStr for $e { + type Err = String; + + fn from_str(s: &str) -> Result::Err> { + match s { + $(stringify!($v) => Ok($e::$v),)+ + _ => Err({ + let v = vec![ + $(stringify!($v),)+ + ]; + format!("valid:{}", v.iter().fold(String::new(),|a, i| a + &format!(" {}", i)[..])) + }) + } + } + } + }; + (pub enum $e:ident { $($v:ident),+ } ) => { + pub enum $e { + $($v),+ + } + + impl std::str::FromStr for $e { + type Err = String; + + fn from_str(s: &str) -> Result::Err> { + match s { + $(stringify!($v) => Ok($e::$v),)+ + _ => Err({ + let v = vec![ + $(stringify!($v),)+ + ]; + format!("valid:{}", v.iter().fold(String::new(),|a, i| a + &format!(" {}", i)[..])) + }) + } + } + } + }; + (#[derive($($d:ident),+)] enum $e:ident { $($v:ident),+ } ) => { + #[derive($($d,)+)] + enum $e { + $($v),+ + } + + impl std::str::FromStr for $e { + type Err = String; + + fn from_str(s: &str) -> Result::Err> { + match s { + $(stringify!($v) => Ok($e::$v),)+ + _ => Err({ + let v = vec![ + $(stringify!($v),)+ + ]; + format!("valid:{}", v.iter().fold(String::new(),|a, i| a + &format!(" {}", i)[..])) + }) + } + } + } + }; + (#[derive($($d:ident),+)] pub enum $e:ident { $($v:ident),+ } ) => { + #[derive($($d,)+)] + pub enum $e { + $($v),+ + } + + impl std::str::FromStr for $e { + type Err = String; + + fn from_str(s: &str) -> Result::Err> { + match s { + $(stringify!($v) => Ok($e::$v),)+ + _ => Err({ + let v = vec![ + $(stringify!($v),)+ + ]; + format!("valid:{}", v.iter().fold(String::new(),|a, i| a + &format!(" {}", i)[..])) + }) } } }