Repository: clap-rs/clap
Pull request #1931 (2020-05-15 07:32:37)

Allow separate derives, but do not give examples
<!--
If your PR closes some issues, please write `Closes #XXXX`
where `XXXX` is the number of the issue you want to fix.
Each issue goes on it's own line.
-->



Patch:
diff --git a/clap_derive/src/derives/arg_enum.rs b/clap_derive/src/derives/arg_enum.rs
index 2b3c1f70..dac2b564 100644
--- a/clap_derive/src/derives/arg_enum.rs
+++ b/clap_derive/src/derives/arg_enum.rs
@@ -10,16 +10,37 @@
 
 use crate::{
     attrs::{Attrs, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
+    dummies,
     utils::Sp,
 };
 
 use proc_macro2::{Span, TokenStream};
+use proc_macro_error::abort_call_site;
 use quote::quote;
 use syn::{
-    punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DataEnum, Ident, Variant,
+    punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataEnum, DeriveInput,
+    Fields, Ident, Variant,
 };
 
+pub fn derive_arg_enum(input: &DeriveInput) -> TokenStream {
+    let ident = &input.ident;
+
+    dummies::arg_enum(ident);
+
+    match input.data {
+        Data::Enum(ref e) => gen_for_enum(ident, &input.attrs, e),
+        _ => abort_call_site!("`#[derive(ArgEnum)]` only supports enums"),
+    }
+}
+
 pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
+    if !e.variants.iter().all(|v| match v.fields {
+        Fields::Unit => true,
+        _ => false,
+    }) {
+        return quote!();
+    };
+
     let attrs = Attrs::from_struct(
         Span::call_site(),
         attrs,
diff --git a/clap_derive/src/derives/clap.rs b/clap_derive/src/derives/clap.rs
index 9c454e11..cd3be9f9 100644
--- a/clap_derive/src/derives/clap.rs
+++ b/clap_derive/src/derives/clap.rs
@@ -13,40 +13,37 @@
 // MIT/Apache 2.0 license.
 
 use crate::{
-    derives::{arg_enum, from_argmatches, into_app, subcommand},
+    derives::{arg_enum, from_arg_matches, into_app, subcommand},
     dummies,
 };
 
 use proc_macro2::TokenStream;
 use proc_macro_error::abort_call_site;
 use quote::quote;
-use syn::{self, punctuated, token, Attribute, DataEnum, DeriveInput, Field, Ident};
+use syn::{
+    self, punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum, DataStruct, DeriveInput,
+    Field, Fields, Ident,
+};
 
 pub fn derive_clap(input: &DeriveInput) -> TokenStream {
-    use syn::Data::*;
-
     let ident = &input.ident;
 
     match input.data {
-        Struct(syn::DataStruct {
-            fields: syn::Fields::Named(ref fields),
+        Data::Struct(DataStruct {
+            fields: Fields::Named(ref fields),
             ..
         }) => {
             dummies::clap_struct(ident);
             gen_for_struct(ident, &fields.named, &input.attrs)
         }
-        Struct(syn::DataStruct {
-            fields: syn::Fields::Unit,
+        Data::Struct(DataStruct {
+            fields: Fields::Unit,
             ..
         }) => {
             dummies::clap_struct(ident);
-            gen_for_struct(
-                ident,
-                &punctuated::Punctuated::<Field, token::Comma>::new(),
-                &input.attrs,
-            )
+            gen_for_struct(ident, &Punctuated::<Field, Comma>::new(), &input.attrs)
         }
-        Enum(ref e) => {
+        Data::Enum(ref e) => {
             dummies::clap_enum(ident);
             gen_for_enum(ident, &input.attrs, e)
         }
@@ -56,11 +53,11 @@ pub fn derive_clap(input: &DeriveInput) -> TokenStream {
 
 fn gen_for_struct(
     name: &Ident,
-    fields: &punctuated::Punctuated<Field, token::Comma>,
+    fields: &Punctuated<Field, Comma>,
     attrs: &[Attribute],
 ) -> TokenStream {
     let (into_app, attrs) = into_app::gen_for_struct(name, fields, attrs);
-    let from_arg_matches = from_argmatches::gen_for_struct(name, fields, &attrs);
+    let from_arg_matches = from_arg_matches::gen_for_struct(name, fields, &attrs);
 
     quote! {
         impl ::clap::Clap for #name {}
@@ -72,20 +69,12 @@ fn gen_for_struct(
 
 fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
     let into_app = into_app::gen_for_enum(name);
-    let from_arg_matches = from_argmatches::gen_for_enum(name);
+    let from_arg_matches = from_arg_matches::gen_for_enum(name);
     let subcommand = subcommand::gen_for_enum(name, attrs, e);
-
-    let arg_enum = if e.variants.iter().all(|v| match v.fields {
-        syn::Fields::Unit => true,
-        _ => false,
-    }) {
-        arg_enum::gen_for_enum(name, attrs, e)
-    } else {
-        quote!()
-    };
+    let arg_enum = arg_enum::gen_for_enum(name, attrs, e);
 
     quote! {
-        impl ::clap::Clap for #name { }
+        impl ::clap::Clap for #name {}
 
         #into_app
         #from_arg_matches
diff --git a/clap_derive/src/derives/from_argmatches.rs b/clap_derive/src/derives/from_arg_matches.rs
similarity index 97%
rename from clap_derive/src/derives/from_argmatches.rs
rename to clap_derive/src/derives/from_arg_matches.rs
index 06dc125d..195d2fe4 100644
--- a/clap_derive/src/derives/from_argmatches.rs
+++ b/clap_derive/src/derives/from_arg_matches.rs
@@ -14,7 +14,7 @@
 use proc_macro2::TokenStream;
 use proc_macro_error::abort;
 use quote::{quote, quote_spanned};
-use syn::{punctuated::Punctuated, spanned::Spanned, Field, Ident, Token, Type};
+use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Field, Ident, Type};
 
 use crate::{
     attrs::{Attrs, Kind, ParserKind},
@@ -23,7 +23,7 @@ use crate::{
 
 pub fn gen_for_struct(
     struct_name: &Ident,
-    fields: &Punctuated<Field, Token![,]>,
+    fields: &Punctuated<Field, Comma>,
     parent_attribute: &Attrs,
 ) -> TokenStream {
     let constructor = gen_constructor(fields, parent_attribute);
@@ -81,10 +81,7 @@ fn gen_arg_enum_parse(ty: &Type, attrs: &Attrs) -> TokenStream {
     }
 }
 
-pub fn gen_constructor(
-    fields: &Punctuated<Field, Token![,]>,
-    parent_attribute: &Attrs,
-) -> TokenStream {
+pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
     let fields = fields.iter().map(|field| {
         let attrs = Attrs::from_field(
             field,
diff --git a/clap_derive/src/derives/into_app.rs b/clap_derive/src/derives/into_app.rs
index 747f270d..4570f749 100644
--- a/clap_derive/src/derives/into_app.rs
+++ b/clap_derive/src/derives/into_app.rs
@@ -14,19 +14,42 @@
 
 use std::env;
 
-use proc_macro2::TokenStream;
-use proc_macro_error::abort;
+use proc_macro2::{Span, TokenStream};
+use proc_macro_error::{abort, abort_call_site};
 use quote::{quote, quote_spanned};
-use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Field, Ident, Token, Type};
+use syn::{
+    punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataStruct,
+    DeriveInput, Field, Fields, Ident, Type,
+};
 
 use crate::{
     attrs::{Attrs, GenOutput, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING},
+    dummies,
     utils::{sub_type, subty_if_name, Sp, Ty},
 };
 
+pub fn derive_into_app(input: &DeriveInput) -> TokenStream {
+    let ident = &input.ident;
+
+    dummies::into_app(ident);
+
+    match input.data {
+        Data::Struct(DataStruct {
+            fields: Fields::Named(ref fields),
+            ..
+        }) => gen_for_struct(ident, &fields.named, &input.attrs).0,
+        Data::Struct(DataStruct {
+            fields: Fields::Unit,
+            ..
+        }) => gen_for_struct(ident, &Punctuated::<Field, Comma>::new(), &input.attrs).0,
+        Data::Enum(_) => gen_for_enum(ident),
+        _ => abort_call_site!("`#[derive(IntoApp)]` only supports non-tuple structs and enums"),
+    }
+}
+
 pub fn gen_for_struct(
     struct_name: &Ident,
-    fields: &Punctuated<Field, Token![,]>,
+    fields: &Punctuated<Field, Comma>,
     attrs: &[Attribute],
 ) -> GenOutput {
     let (into_app, attrs) = gen_into_app_fn(attrs);
@@ -88,7 +111,7 @@ fn gen_into_app_fn(attrs: &[Attribute]) -> GenOutput {
     let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
 
     let attrs = Attrs::from_struct(
-        proc_macro2::Span::call_site(),
+        Span::call_site(),
         attrs,
         Name::Assigned(quote!(#app_name)),
         Sp::call_site(DEFAULT_CASING),
@@ -105,11 +128,8 @@ fn gen_into_app_fn(attrs: &[Attribute]) -> GenOutput {
     (tokens, attrs)
 }
 
-fn gen_augment_clap_fn(
-    fields: &Punctuated<Field, Token![,]>,
-    parent_attribute: &Attrs,
-) -> TokenStream {
-    let app_var = Ident::new("app", proc_macro2::Span::call_site());
+fn gen_augment_clap_fn(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
+    let app_var = Ident::new("app", Span::call_site());
     let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute);
     quote! {
         fn augment_clap<'b>(#app_var: ::clap::App<'b>) -> ::clap::App<'b> {
@@ -127,7 +147,7 @@ fn gen_arg_enum_possible_values(ty: &Type) -> TokenStream {
 /// Generate a block of code to add arguments/subcommands corresponding to
 /// the `fields` to an app.
 pub fn gen_app_augmentation(
-    fields: &Punctuated<Field, Token![,]>,
+    fields: &Punctuated<Field, Comma>,
     app_var: &Ident,
     parent_attribute: &Attrs,
 ) -> TokenStream {
diff --git a/clap_derive/src/derives/mod.rs b/clap_derive/src/derives/mod.rs
index 8fe7aed2..ea94eb7f 100644
--- a/clap_derive/src/derives/mod.rs
+++ b/clap_derive/src/derives/mod.rs
@@ -13,8 +13,12 @@
 // MIT/Apache 2.0 license.
 mod arg_enum;
 mod clap;
-mod from_argmatches;
+mod from_arg_matches;
 mod into_app;
 mod subcommand;
 
 pub use self::clap::derive_clap;
+pub use arg_enum::derive_arg_enum;
+// pub use from_arg_matches::derive_from_arg_matches;
+pub use into_app::derive_into_app;
+pub use subcommand::derive_subcommand;
diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs
index ccee7c6c..40e6ce45 100644
--- a/clap_derive/src/derives/subcommand.rs
+++ b/clap_derive/src/derives/subcommand.rs
@@ -1,6 +1,7 @@
 use crate::{
     attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
-    derives::{from_argmatches, into_app},
+    derives::{from_arg_matches, into_app},
+    dummies,
     utils::{is_simple_ty, subty_if_name, Sp},
 };
 
@@ -8,9 +9,21 @@ use proc_macro2::{Ident, Span, TokenStream};
 use proc_macro_error::{abort, abort_call_site};
 use quote::{quote, quote_spanned};
 use syn::{
-    punctuated::Punctuated, spanned::Spanned, Attribute, DataEnum, FieldsUnnamed, Token, Variant,
+    punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput,
+    FieldsUnnamed, Token, Variant,
 };
 
+pub fn derive_subcommand(input: &DeriveInput) -> TokenStream {
+    let ident = &input.ident;
+
+    dummies::subcommand(ident);
+
+    match input.data {
+        Data::Enum(ref e) => gen_for_enum(ident, &input.attrs, e),
+        _ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"),
+    }
+}
+
 pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
     let attrs = Attrs::from_struct(
         Span::call_site(),
@@ -209,7 +222,7 @@ fn gen_from_subcommand(
         let sub_name = attrs.cased_name();
         let variant_name = &variant.ident;
         let constructor_block = match variant.fields {
-            Named(ref fields) => from_argmatches::gen_constructor(&fields.named, &attrs),
+            Named(ref fields) => from_arg_matches::gen_constructor(&fields.named, &attrs),
             Unit => quote!(),
             Unnamed(ref fields) if fields.unnamed.len() == 1 => {
                 let ty = &fields.unnamed[0];
diff --git a/clap_derive/src/lib.rs b/clap_derive/src/lib.rs
index 68244122..3e13d10e 100644
--- a/clap_derive/src/lib.rs
+++ b/clap_derive/src/lib.rs
@@ -18,7 +18,9 @@
 
 extern crate proc_macro;
 
+use proc_macro::TokenStream;
 use proc_macro_error::proc_macro_error;
+use syn::{parse_macro_input, DeriveInput};
 
 mod attrs;
 mod derives;
@@ -26,17 +28,42 @@ mod dummies;
 mod parse;
 mod utils;
 
-// /// It is required to have this seperate and specificly defined.
-// #[proc_macro_derive(ArgEnum, attributes(case_sensitive))]
-// pub fn arg_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-//     let input: syn::DeriveInput = syn::parse(input).unwrap();
-//     derives::derive_arg_enum(&input).into()
-// }
+/// Generates the `ArgEnum` impl.
+#[proc_macro_derive(ArgEnum, attributes(clap))]
+#[proc_macro_error]
+pub fn arg_enum(input: TokenStream) -> TokenStream {
+    let input: DeriveInput = parse_macro_input!(input);
+    derives::derive_arg_enum(&input).into()
+}
 
 /// Generates the `Clap` impl.
 #[proc_macro_derive(Clap, attributes(clap))]
 #[proc_macro_error]
-pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input: syn::DeriveInput = syn::parse_macro_input!(input);
+pub fn clap(input: TokenStream) -> TokenStream {
+    let input: DeriveInput = parse_macro_input!(input);
     derives::derive_clap(&input).into()
 }
+
+// /// Generates the `FromArgMatches` impl.
+// #[proc_macro_derive(FromArgMatches, attributes(clap))]
+// #[proc_macro_error]
+// pub fn from_arg_matches(input: TokenStream) -> TokenStream {
+//     let input: DeriveInput = parse_macro_input!(input);
+//     derives::derive_from_arg_matches(&input).into()
+// }
+
+/// Generates the `IntoApp` impl.
+#[proc_macro_derive(IntoApp, attributes(clap))]
+#[proc_macro_error]
+pub fn into_app(input: TokenStream) -> TokenStream {
+    let input: DeriveInput = parse_macro_input!(input);
+    derives::derive_into_app(&input).into()
+}
+
+/// Generates the `Subcommand` impl.
+#[proc_macro_derive(Subcommand, attributes(clap))]
+#[proc_macro_error]
+pub fn subcommand(input: TokenStream) -> TokenStream {
+    let input: DeriveInput = parse_macro_input!(input);
+    derives::derive_subcommand(&input).into()
+}
diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs
index fada5a99..abbc22e7 100644
--- a/src/build/app/mod.rs
+++ b/src/build/app/mod.rs
@@ -2190,7 +2190,7 @@ impl<'a> From<&'a Yaml> for App<'a> {
         }
         if let Some(v) = yaml["groups"].as_vec() {
             for ag_yaml in v {
-                a = a.group(ArgGroup::from(ag_yaml));
+                a = a.group(ArgGroup::from(ag_yaml.as_hash().unwrap()));
             }
         }
 
diff --git a/src/build/arg_group.rs b/src/build/arg_group.rs
index a22dfa28..c9d93199 100644
--- a/src/build/arg_group.rs
+++ b/src/build/arg_group.rs
@@ -414,6 +414,22 @@ impl<'a> Debug for ArgGroup<'a> {
     }
 }
 
+#[cfg(feature = "yaml")]
+impl<'a> From<&'a Yaml> for ArgGroup<'a> {
+    /// Creates a new instance of `ArgGroup` from a .yml (YAML) file.
+    ///
+    /// # Examples
+    ///
+    /// ```ignore
+    /// # use clap::{ArgGroup, load_yaml};
+    /// let yml = load_yaml!("group.yml");
+    /// let ag = ArgGroup::from(yml);
+    /// ```
+    fn from(y: &'a Yaml) -> Self {
+        ArgGroup::from(y.as_hash().unwrap())
+    }
+}
+
 impl<'a, 'z> From<&'z ArgGroup<'a>> for ArgGroup<'a> {
     fn from(g: &'z ArgGroup<'a>) -> Self {
         ArgGroup {
@@ -429,18 +445,8 @@ impl<'a, 'z> From<&'z ArgGroup<'a>> for ArgGroup<'a> {
 }
 
 #[cfg(feature = "yaml")]
-impl<'a> From<&'a Yaml> for ArgGroup<'a> {
-    /// Creates a new instance of `ArgGroup` from a .yml (YAML) file.
-    ///
-    /// # Examples
-    ///
-    /// ```ignore
-    /// # use clap::{ArgGroup, load_yaml};
-    /// let yml = load_yaml!("group.yml");
-    /// let ag = ArgGroup::from(yml);
-    /// ```
-    fn from(y: &'a Yaml) -> Self {
-        let b = y.as_hash().expect("ArgGroup::from::<Yaml> expects a table");
+impl<'a> From<&'a yaml_rust::yaml::Hash> for ArgGroup<'a> {
+    fn from(b: &'a yaml_rust::yaml::Hash) -> Self {
         // We WANT this to panic on error...so expect() is good.
         let mut a = ArgGroup::default();
         let group_settings = if b.len() == 1 {
diff --git a/src/lib.rs b/src/lib.rs
index 17fd2cd3..fdd280f0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,7 +29,6 @@ pub use crate::{
 };
 
 #[cfg(feature = "yaml")]
-#[cfg_attr(feature = "yaml", doc(hidden))]
 pub use yaml_rust::YamlLoader;
 
 #[cfg(feature = "derive")]
@@ -42,9 +41,9 @@ pub use lazy_static;
 
 #[macro_use]
 #[allow(missing_docs)]
-pub mod macros;
+mod macros;
 
-pub mod derive;
+mod derive;
 
 mod build;
 mod mkeymap;


Test patch:
diff --git a/clap_derive/tests/ui/arg_enum_on_struct.rs b/clap_derive/tests/ui/arg_enum_on_struct.rs
new file mode 100644
index 00000000..cb41f72b
--- /dev/null
+++ b/clap_derive/tests/ui/arg_enum_on_struct.rs
@@ -0,0 +1,8 @@
+use clap::ArgEnum;
+
+#[derive(ArgEnum, Debug)]
+struct Opt {}
+
+fn main() {
+    println!("{:?}", Opt::VARIANTS);
+}
diff --git a/clap_derive/tests/ui/arg_enum_on_struct.stderr b/clap_derive/tests/ui/arg_enum_on_struct.stderr
new file mode 100644
index 00000000..b64ac779
--- /dev/null
+++ b/clap_derive/tests/ui/arg_enum_on_struct.stderr
@@ -0,0 +1,7 @@
+error: `#[derive(ArgEnum)]` only supports enums
+ --> $DIR/arg_enum_on_struct.rs:3:10
+  |
+3 | #[derive(ArgEnum, Debug)]
+  |          ^^^^^^^
+  |
+  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/clap_derive/tests/ui/subcommand_on_struct.rs b/clap_derive/tests/ui/subcommand_on_struct.rs
new file mode 100644
index 00000000..241d7c7d
--- /dev/null
+++ b/clap_derive/tests/ui/subcommand_on_struct.rs
@@ -0,0 +1,6 @@
+use clap::Subcommand;
+
+#[derive(Subcommand, Debug)]
+struct Opt {}
+
+fn main() {}
diff --git a/clap_derive/tests/ui/subcommand_on_struct.stderr b/clap_derive/tests/ui/subcommand_on_struct.stderr
new file mode 100644
index 00000000..14b03d89
--- /dev/null
+++ b/clap_derive/tests/ui/subcommand_on_struct.stderr
@@ -0,0 +1,7 @@
+error: `#[derive(Subcommand)]` only supports enums
+ --> $DIR/subcommand_on_struct.rs:3:10
+  |
+3 | #[derive(Subcommand, Debug)]
+  |          ^^^^^^^^^^
+  |
+  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)