Skip to content

Commit

Permalink
Add EnumVariantNames (#56)
Browse files Browse the repository at this point in the history
* Add `EnumVariantNames`

This derive adds a static `variants()` methods yielding the names of
the enum variants. This happens to be exactly what clap [wants][1], and
is the last puzzle piece to use strum with clap/structopt.

[1]: https://docs.rs/clap/2.33.0/clap/macro.arg_enum.html

* Expand readme section for `EnumVariantNames`

* Return slice instead of array

This reduces the risk of breaking the public API by accident when adding
a new variant.

* Fix typo in Readme

* Add generic doc comment to `variants()` method

* Add test case using `variants` in clap/structopt
  • Loading branch information
killercup authored and Peternator7 committed Jul 15, 2019
1 parent 970b5ce commit 96daaf4
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 1 deletion.
50 changes: 50 additions & 0 deletions README.md
Expand Up @@ -61,6 +61,7 @@ Strum has implemented the following macros:
| [Display](#Display) | Converts enum variants to strings |
| [AsRefStr](#AsRefStr) | Converts enum variants to `&'static str` |
| [IntoStaticStr](#IntoStaticStr) | Implements `From<MyEnum> for &'static str` on an enum |
| [EnumVariantNames](#EnumVariantNames) | Adds a `variants` method returning an array of discriminant names |
| [EnumIter](#EnumIter) | Creates a new type that iterates of the variants of an enum. |
| [EnumProperty](#EnumProperty) | Add custom properties to enum variants. |
| [EnumMessage](#EnumMessage) | Add a verbose message to an enum variant. |
Expand Down Expand Up @@ -191,6 +192,55 @@ fn main() {
}
```

## EnumVariantNames

Adds an `impl` block for the `enum` that adds a static `variants()` method that returns an array of `&'static str` that are the discriminant names.
This will respect the `serialize_all` attribute on the `enum` (like `#[strum(serialize_all = "snake_case")]`, see **Additional Attributes** below).

**Note:** This is compatible with the format [clap](https://docs.rs/clap/2) expects for `enums`, meaning this works:

```rust
use strum::{EnumString, EnumVariantNames};

#[derive(EnumString, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
enum Color {
Red,
Blue,
Yellow,
RebeccaPurple,
}

fn main() {
// This is what you get:
assert_eq!(
&Color::variants(),
&["red", "blue", "yellow", "rebecca-purple"]
);

// Use it with clap like this:
let args = clap::App::new("app")
.arg(Arg::with_name("color")
.long("color")
.possible_values(&Color::variants())
.case_insensitive(true))
.get_matches();

// ...
}
```

This also works with [structopt](https://docs.rs/structopt/0.2) (assuming the same definition of `Color` as above):

```rust
#[derive(Debug, StructOpt)]
struct Cli {
/// The main color
#[structopt(long = "color", default_value = "Color::Blue", raw(possible_values = "&Color::variants()"))]
color: Color,
}
```

## EnumIter

Iterate over the variants of an Enum. Any additional data on your variants will be set to `Default::default()`.
Expand Down
1 change: 1 addition & 0 deletions strum_macros/Cargo.toml
Expand Up @@ -25,6 +25,7 @@ syn = { version = "0.15", features = ["parsing", "extra-traits"] }
[features]
verbose-enumstring-name = []
verbose-asrefstr-name = []
verbose-variant-names = []
verbose-asstaticstr-name = []
verbose-intostaticstr-name = []
verbose-tostring-name = []
Expand Down
36 changes: 36 additions & 0 deletions strum_macros/src/enum_variant_names.rs
@@ -0,0 +1,36 @@
use proc_macro2::TokenStream;
use syn;

use case_style::CaseStyle;
use helpers::{convert_case, extract_meta, unique_attr};

pub fn enum_variant_names_inner(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;

let variants = match ast.data {
syn::Data::Enum(ref v) => &v.variants,
_ => panic!("EnumVariantNames only works on Enums"),
};

// Derives for the generated enum
let type_meta = extract_meta(&ast.attrs);
let case_style = unique_attr(&type_meta, "strum", "serialize_all")
.map(|style| CaseStyle::from(style.as_ref()));

let names = variants
.iter()
.map(|v| convert_case(&v.ident, case_style))
.collect::<Vec<_>>();

quote! {
impl #name {
/// Return a slice containing the names of the variants of this enum
#[allow(dead_code)]
pub fn variants() -> &'static [&'static str] {
&[
#(#names),*
]
}
}
}
}
11 changes: 11 additions & 0 deletions strum_macros/src/lib.rs
Expand Up @@ -22,6 +22,7 @@ mod case_style;
mod display;
mod enum_count;
mod enum_discriminants;
mod enum_variant_names;
mod enum_iter;
mod enum_messages;
mod enum_properties;
Expand Down Expand Up @@ -65,6 +66,16 @@ pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
toks.into()
}

#[cfg_attr(not(feature = "verbose-variant-names"), proc_macro_derive(EnumVariantNames, attributes(strum)))]
#[cfg_attr(feature = "verbose-variant-names", proc_macro_derive(StrumEnumVariantNames, attributes(strum)))]
pub fn variant_names(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse(input).unwrap();

let toks = enum_variant_names::enum_variant_names_inner(&ast);
debug_print_generated(&ast, &toks);
toks.into()
}

#[cfg_attr(feature = "verbose-asstaticstr-name", proc_macro_derive(StrumAsStaticStr, attributes(strum)))]
#[cfg_attr(not(feature = "verbose-asstaticstr-name"), proc_macro_derive(AsStaticStr, attributes(strum)))]
pub fn as_static_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Expand Down
4 changes: 3 additions & 1 deletion strum_tests/Cargo.toml
Expand Up @@ -5,4 +5,6 @@ authors = ["Peter Glotfelty <peglotfe@microsoft.com>"]

[dependencies]
strum = { path = "../strum" }
strum_macros = { path = "../strum_macros", features = [] }
strum_macros = { path = "../strum_macros", features = [] }
clap = "2.33.0"
structopt = "0.2.18"
90 changes: 90 additions & 0 deletions strum_tests/tests/enum_variant_names.rs
@@ -0,0 +1,90 @@
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate structopt;
extern crate strum;

#[test]
fn simple() {
#[allow(dead_code)]
#[derive(EnumVariantNames)]
enum Color {
Red,
Blue,
Yellow,
}

assert_eq!(&Color::variants(), &["Red", "Blue", "Yellow"]);
}

#[test]
fn plain_kebab() {
#[allow(dead_code)]
#[derive(EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
enum Color {
Red,
Blue,
Yellow,
RebeccaPurple,
}

assert_eq!(
&Color::variants(),
&["red", "blue", "yellow", "rebecca-purple"]
);
}

#[test]
fn non_plain_camel() {
#[allow(dead_code)]
#[derive(EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
enum Color {
DeepPink,
GreenYellow,
CornflowerBlue,
Other { r: u8, g: u8, b: u8 },
}

assert_eq!(
&Color::variants(),
&["deep-pink", "green-yellow", "cornflower-blue", "other"]
);
}

#[test]
fn clap_and_structopt() {
#[derive(Debug, EnumString, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
enum Color {
Red,
Blue,
Yellow,
RebeccaPurple,
}

assert_eq!(
&Color::variants(),
&["red", "blue", "yellow", "rebecca-purple"]
);

let _clap_example = clap::App::new("app").arg(
clap::Arg::with_name("color")
.long("color")
.possible_values(Color::variants())
.case_insensitive(true),
);

#[derive(Debug, StructOpt)]
#[allow(unused)]
struct StructOptExample {
/// The main color
#[structopt(
long = "color",
default_value = "Color::Blue",
raw(possible_values = "Color::variants()")
)]
color: Color,
}
}

0 comments on commit 96daaf4

Please sign in to comment.