aboutsummaryrefslogtreecommitdiff
path: root/structopt/structopt-derive/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'structopt/structopt-derive/src/lib.rs')
-rw-r--r--structopt/structopt-derive/src/lib.rs667
1 files changed, 667 insertions, 0 deletions
diff --git a/structopt/structopt-derive/src/lib.rs b/structopt/structopt-derive/src/lib.rs
new file mode 100644
index 0000000..87eaf1f
--- /dev/null
+++ b/structopt/structopt-derive/src/lib.rs
@@ -0,0 +1,667 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! This crate is custom derive for `StructOpt`. It should not be used
+//! directly. See [structopt documentation](https://docs.rs/structopt)
+//! for the usage of `#[derive(StructOpt)]`.
+
+#![allow(clippy::large_enum_variant)]
+
+extern crate proc_macro;
+
+mod attrs;
+mod doc_comments;
+mod parse;
+mod spanned;
+mod ty;
+
+use crate::{
+ attrs::{Attrs, CasingStyle, Kind, Name, ParserKind},
+ spanned::Sp,
+ ty::{sub_type, Ty},
+};
+
+use proc_macro2::{Span, TokenStream};
+use proc_macro_error::{abort, abort_call_site, proc_macro_error, set_dummy};
+use quote::{quote, quote_spanned};
+use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, *};
+
+/// Default casing style for generated arguments.
+const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
+
+/// Default casing style for environment variables
+const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake;
+
+/// Output for the `gen_xxx()` methods were we need more than a simple stream of tokens.
+///
+/// The output of a generation method is not only the stream of new tokens but also the attribute
+/// information of the current element. These attribute information may contain valuable information
+/// for any kind of child arguments.
+struct GenOutput {
+ tokens: TokenStream,
+ attrs: Attrs,
+}
+
+/// Generates the `StructOpt` impl.
+#[proc_macro_derive(StructOpt, attributes(structopt))]
+#[proc_macro_error]
+pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input: DeriveInput = syn::parse(input).unwrap();
+ let gen = impl_structopt(&input);
+ gen.into()
+}
+
+/// Generate a block of code to add arguments/subcommands corresponding to
+/// the `fields` to an app.
+fn gen_augmentation(
+ fields: &Punctuated<Field, Comma>,
+ app_var: &Ident,
+ parent_attribute: &Attrs,
+) -> TokenStream {
+ let mut subcmds = fields.iter().filter_map(|field| {
+ let attrs = Attrs::from_field(
+ field,
+ parent_attribute.casing(),
+ parent_attribute.env_casing(),
+ );
+ let kind = attrs.kind();
+ if let Kind::Subcommand(ty) = &*kind {
+ let subcmd_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+ let required = if **ty == Ty::Option {
+ quote!()
+ } else {
+ quote_spanned! { kind.span()=>
+ let #app_var = #app_var.setting(
+ ::structopt::clap::AppSettings::SubcommandRequiredElseHelp
+ );
+ }
+ };
+
+ let span = field.span();
+ let ts = quote! {
+ let #app_var = <#subcmd_type as ::structopt::StructOptInternal>::augment_clap(
+ #app_var
+ );
+ #required
+ };
+ Some((span, ts))
+ } else {
+ None
+ }
+ });
+
+ let subcmd = subcmds.next().map(|(_, ts)| ts);
+ if let Some((span, _)) = subcmds.next() {
+ abort!(
+ span,
+ "multiple subcommand sets are not allowed, that's the second"
+ );
+ }
+
+ let args = fields.iter().filter_map(|field| {
+ let attrs = Attrs::from_field(
+ field,
+ parent_attribute.casing(),
+ parent_attribute.env_casing(),
+ );
+ let kind = attrs.kind();
+ match &*kind {
+ Kind::Subcommand(_) | Kind::Skip(_) => None,
+ Kind::FlattenStruct => {
+ let ty = &field.ty;
+ Some(quote_spanned! { kind.span()=>
+ let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(#app_var);
+ let #app_var = if <#ty as ::structopt::StructOptInternal>::is_subcommand() {
+ #app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp)
+ } else {
+ #app_var
+ };
+ })
+ }
+ Kind::Arg(ty) => {
+ let convert_type = match **ty {
+ Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty),
+ Ty::OptionOption | Ty::OptionVec => {
+ sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty)
+ }
+ _ => &field.ty,
+ };
+
+ let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
+ let flag = *attrs.parser().kind == ParserKind::FromFlag;
+
+ let parser = attrs.parser();
+ let func = &parser.func;
+ let validator = match *parser.kind {
+ ParserKind::TryFromStr => quote_spanned! { func.span()=>
+ .validator(|s| {
+ #func(s.as_str())
+ .map(|_: #convert_type| ())
+ .map_err(|e| e.to_string())
+ })
+ },
+ ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
+ .validator_os(|s| #func(&s).map(|_: #convert_type| ()))
+ },
+ _ => quote!(),
+ };
+
+ let modifier = match **ty {
+ Ty::Bool => quote_spanned! { ty.span()=>
+ .takes_value(false)
+ .multiple(false)
+ },
+
+ Ty::Option => quote_spanned! { ty.span()=>
+ .takes_value(true)
+ .multiple(false)
+ #validator
+ },
+
+ Ty::OptionOption => quote_spanned! { ty.span()=>
+ .takes_value(true)
+ .multiple(false)
+ .min_values(0)
+ .max_values(1)
+ #validator
+ },
+
+ Ty::OptionVec => quote_spanned! { ty.span()=>
+ .takes_value(true)
+ .multiple(true)
+ .min_values(0)
+ #validator
+ },
+
+ Ty::Vec => quote_spanned! { ty.span()=>
+ .takes_value(true)
+ .multiple(true)
+ #validator
+ },
+
+ Ty::Other if occurrences => quote_spanned! { ty.span()=>
+ .takes_value(false)
+ .multiple(true)
+ },
+
+ Ty::Other if flag => quote_spanned! { ty.span()=>
+ .takes_value(false)
+ .multiple(false)
+ },
+
+ Ty::Other => {
+ let required = !attrs.has_method("default_value");
+ quote_spanned! { ty.span()=>
+ .takes_value(true)
+ .multiple(false)
+ .required(#required)
+ #validator
+ }
+ }
+ };
+
+ let name = attrs.cased_name();
+ let methods = attrs.field_methods();
+
+ Some(quote_spanned! { field.span()=>
+ let #app_var = #app_var.arg(
+ ::structopt::clap::Arg::with_name(#name)
+ #modifier
+ #methods
+ );
+ })
+ }
+ }
+ });
+
+ let app_methods = parent_attribute.top_level_methods();
+ quote! {{
+ let #app_var = #app_var#app_methods;
+ #( #args )*
+ #subcmd
+ #app_var
+ }}
+}
+
+fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
+ let fields = fields.iter().map(|field| {
+ let attrs = Attrs::from_field(
+ field,
+ parent_attribute.casing(),
+ parent_attribute.env_casing(),
+ );
+ let field_name = field.ident.as_ref().unwrap();
+ let kind = attrs.kind();
+ match &*kind {
+ Kind::Subcommand(ty) => {
+ let subcmd_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+ let unwrapper = match **ty {
+ Ty::Option => quote!(),
+ _ => quote_spanned!( ty.span()=> .unwrap() ),
+ };
+ quote_spanned! { kind.span()=>
+ #field_name: <#subcmd_type as ::structopt::StructOptInternal>::from_subcommand(
+ matches.subcommand())
+ #unwrapper
+ }
+ }
+
+ Kind::FlattenStruct => quote_spanned! { kind.span()=>
+ #field_name: ::structopt::StructOpt::from_clap(matches)
+ },
+
+ Kind::Skip(val) => match val {
+ None => quote_spanned!(kind.span()=> #field_name: Default::default()),
+ Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
+ },
+
+ Kind::Arg(ty) => {
+ use crate::attrs::ParserKind::*;
+
+ let parser = attrs.parser();
+ let func = &parser.func;
+ let span = parser.kind.span();
+ let (value_of, values_of, parse) = match *parser.kind {
+ FromStr => (
+ quote_spanned!(span=> value_of),
+ quote_spanned!(span=> values_of),
+ func.clone(),
+ ),
+ TryFromStr => (
+ quote_spanned!(span=> value_of),
+ quote_spanned!(span=> values_of),
+ quote_spanned!(func.span()=> |s| #func(s).unwrap()),
+ ),
+ FromOsStr => (
+ quote_spanned!(span=> value_of_os),
+ quote_spanned!(span=> values_of_os),
+ func.clone(),
+ ),
+ TryFromOsStr => (
+ quote_spanned!(span=> value_of_os),
+ quote_spanned!(span=> values_of_os),
+ quote_spanned!(func.span()=> |s| #func(s).unwrap()),
+ ),
+ FromOccurrences => (
+ quote_spanned!(span=> occurrences_of),
+ quote!(),
+ func.clone(),
+ ),
+ FromFlag => (quote!(), quote!(), func.clone()),
+ };
+
+ let flag = *attrs.parser().kind == ParserKind::FromFlag;
+ let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
+ let name = attrs.cased_name();
+ let field_value = match **ty {
+ Ty::Bool => quote_spanned!(ty.span()=> matches.is_present(#name)),
+
+ Ty::Option => quote_spanned! { ty.span()=>
+ matches.#value_of(#name)
+ .map(#parse)
+ },
+
+ Ty::OptionOption => quote_spanned! { ty.span()=>
+ if matches.is_present(#name) {
+ Some(matches.#value_of(#name).map(#parse))
+ } else {
+ None
+ }
+ },
+
+ Ty::OptionVec => quote_spanned! { ty.span()=>
+ if matches.is_present(#name) {
+ Some(matches.#values_of(#name)
+ .map_or_else(Vec::new, |v| v.map(#parse).collect()))
+ } else {
+ None
+ }
+ },
+
+ Ty::Vec => quote_spanned! { ty.span()=>
+ matches.#values_of(#name)
+ .map_or_else(Vec::new, |v| v.map(#parse).collect())
+ },
+
+ Ty::Other if occurrences => quote_spanned! { ty.span()=>
+ #parse(matches.#value_of(#name))
+ },
+
+ Ty::Other if flag => quote_spanned! { ty.span()=>
+ #parse(matches.is_present(#name))
+ },
+
+ Ty::Other => quote_spanned! { ty.span()=>
+ matches.#value_of(#name)
+ .map(#parse)
+ .unwrap()
+ },
+ };
+
+ quote_spanned!(field.span()=> #field_name: #field_value )
+ }
+ }
+ });
+
+ quote! {{
+ #( #fields ),*
+ }}
+}
+
+fn gen_from_clap(
+ struct_name: &Ident,
+ fields: &Punctuated<Field, Comma>,
+ parent_attribute: &Attrs,
+) -> TokenStream {
+ let field_block = gen_constructor(fields, parent_attribute);
+
+ quote! {
+ fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
+ #struct_name #field_block
+ }
+ }
+}
+
+fn gen_clap(attrs: &[Attribute]) -> GenOutput {
+ let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
+
+ let attrs = Attrs::from_struct(
+ Span::call_site(),
+ attrs,
+ Name::Assigned(LitStr::new(&name, Span::call_site())),
+ Sp::call_site(DEFAULT_CASING),
+ Sp::call_site(DEFAULT_ENV_CASING),
+ );
+ let tokens = {
+ let name = attrs.cased_name();
+ quote!(::structopt::clap::App::new(#name))
+ };
+
+ GenOutput { tokens, attrs }
+}
+
+fn gen_clap_struct(struct_attrs: &[Attribute]) -> GenOutput {
+ let initial_clap_app_gen = gen_clap(struct_attrs);
+ let clap_tokens = initial_clap_app_gen.tokens;
+
+ let augmented_tokens = quote! {
+ fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
+ let app = #clap_tokens;
+ <Self as ::structopt::StructOptInternal>::augment_clap(app)
+ }
+ };
+
+ GenOutput {
+ tokens: augmented_tokens,
+ attrs: initial_clap_app_gen.attrs,
+ }
+}
+
+fn gen_augment_clap(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
+ let app_var = Ident::new("app", Span::call_site());
+ let augmentation = gen_augmentation(fields, &app_var, parent_attribute);
+ quote! {
+ fn augment_clap<'a, 'b>(
+ #app_var: ::structopt::clap::App<'a, 'b>
+ ) -> ::structopt::clap::App<'a, 'b> {
+ #augmentation
+ }
+ }
+}
+
+fn gen_clap_enum(enum_attrs: &[Attribute]) -> GenOutput {
+ let initial_clap_app_gen = gen_clap(enum_attrs);
+ let clap_tokens = initial_clap_app_gen.tokens;
+
+ let tokens = quote! {
+ fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
+ let app = #clap_tokens
+ .setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp);
+ <Self as ::structopt::StructOptInternal>::augment_clap(app)
+ }
+ };
+
+ GenOutput {
+ tokens,
+ attrs: initial_clap_app_gen.attrs,
+ }
+}
+
+fn gen_augment_clap_enum(
+ variants: &Punctuated<Variant, Comma>,
+ parent_attribute: &Attrs,
+) -> TokenStream {
+ use syn::Fields::*;
+
+ let subcommands = variants.iter().map(|variant| {
+ let attrs = Attrs::from_struct(
+ variant.span(),
+ &variant.attrs,
+ Name::Derived(variant.ident.clone()),
+ parent_attribute.casing(),
+ parent_attribute.env_casing(),
+ );
+ let app_var = Ident::new("subcommand", Span::call_site());
+ let arg_block = match variant.fields {
+ Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs),
+ Unit => quote!( #app_var ),
+ Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
+ let ty = &unnamed[0];
+ quote_spanned! { ty.span()=>
+ {
+ let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(
+ #app_var
+ );
+ if <#ty as ::structopt::StructOptInternal>::is_subcommand() {
+ #app_var.setting(
+ ::structopt::clap::AppSettings::SubcommandRequiredElseHelp
+ )
+ } else {
+ #app_var
+ }
+ }
+ }
+ }
+ Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
+ };
+
+ let name = attrs.cased_name();
+ let from_attrs = attrs.top_level_methods();
+
+ quote! {
+ .subcommand({
+ let #app_var = ::structopt::clap::SubCommand::with_name(#name);
+ let #app_var = #arg_block;
+ #app_var#from_attrs
+ })
+ }
+ });
+
+ let app_methods = parent_attribute.top_level_methods();
+
+ quote! {
+ fn augment_clap<'a, 'b>(
+ app: ::structopt::clap::App<'a, 'b>
+ ) -> ::structopt::clap::App<'a, 'b> {
+ app #app_methods #( #subcommands )*
+ }
+ }
+}
+
+fn gen_from_clap_enum(name: &Ident) -> TokenStream {
+ quote! {
+ fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
+ <#name as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand())
+ .unwrap()
+ }
+ }
+}
+
+fn gen_from_subcommand(
+ name: &Ident,
+ variants: &Punctuated<Variant, Comma>,
+ parent_attribute: &Attrs,
+) -> TokenStream {
+ use syn::Fields::*;
+
+ let match_arms = variants.iter().map(|variant| {
+ let attrs = Attrs::from_struct(
+ variant.span(),
+ &variant.attrs,
+ Name::Derived(variant.ident.clone()),
+ parent_attribute.casing(),
+ parent_attribute.env_casing(),
+ );
+ let sub_name = attrs.cased_name();
+ let variant_name = &variant.ident;
+ let constructor_block = match variant.fields {
+ Named(ref fields) => gen_constructor(&fields.named, &attrs),
+ Unit => quote!(),
+ Unnamed(ref fields) if fields.unnamed.len() == 1 => {
+ let ty = &fields.unnamed[0];
+ quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) )
+ }
+ Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
+ };
+
+ quote! {
+ (#sub_name, Some(matches)) =>
+ Some(#name :: #variant_name #constructor_block)
+ }
+ });
+
+ quote! {
+ fn from_subcommand<'a, 'b>(
+ sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>)
+ ) -> Option<Self> {
+ match sub {
+ #( #match_arms ),*,
+ _ => None
+ }
+ }
+ }
+}
+
+#[cfg(feature = "paw")]
+fn gen_paw_impl(name: &Ident) -> TokenStream {
+ quote! {
+ impl paw::ParseArgs for #name {
+ type Error = std::io::Error;
+
+ fn parse_args() -> std::result::Result<Self, Self::Error> {
+ Ok(<#name as ::structopt::StructOpt>::from_args())
+ }
+ }
+ }
+}
+#[cfg(not(feature = "paw"))]
+fn gen_paw_impl(_: &Ident) -> TokenStream {
+ TokenStream::new()
+}
+
+fn impl_structopt_for_struct(
+ name: &Ident,
+ fields: &Punctuated<Field, Comma>,
+ attrs: &[Attribute],
+) -> TokenStream {
+ let basic_clap_app_gen = gen_clap_struct(attrs);
+ let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs);
+ let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs);
+ let paw_impl = gen_paw_impl(name);
+
+ let clap_tokens = basic_clap_app_gen.tokens;
+ quote! {
+ #[allow(unused_variables)]
+ #[allow(unknown_lints)]
+ #[allow(clippy::all)]
+ #[allow(dead_code, unreachable_code)]
+ impl ::structopt::StructOpt for #name {
+ #clap_tokens
+ #from_clap
+ }
+
+ #[allow(unused_variables)]
+ #[allow(unknown_lints)]
+ #[allow(clippy::all)]
+ #[allow(dead_code, unreachable_code)]
+ impl ::structopt::StructOptInternal for #name {
+ #augment_clap
+ fn is_subcommand() -> bool { false }
+ }
+
+ #paw_impl
+ }
+}
+
+fn impl_structopt_for_enum(
+ name: &Ident,
+ variants: &Punctuated<Variant, Comma>,
+ attrs: &[Attribute],
+) -> TokenStream {
+ let basic_clap_app_gen = gen_clap_enum(attrs);
+
+ let augment_clap = gen_augment_clap_enum(variants, &basic_clap_app_gen.attrs);
+ let from_clap = gen_from_clap_enum(name);
+ let from_subcommand = gen_from_subcommand(name, variants, &basic_clap_app_gen.attrs);
+ let paw_impl = gen_paw_impl(name);
+
+ let clap_tokens = basic_clap_app_gen.tokens;
+ quote! {
+ #[allow(unknown_lints)]
+ #[allow(unused_variables, dead_code, unreachable_code)]
+ #[allow(clippy)]
+ impl ::structopt::StructOpt for #name {
+ #clap_tokens
+ #from_clap
+ }
+
+ #[allow(unused_variables)]
+ #[allow(unknown_lints)]
+ #[allow(clippy::all)]
+ #[allow(dead_code, unreachable_code)]
+ impl ::structopt::StructOptInternal for #name {
+ #augment_clap
+ #from_subcommand
+ fn is_subcommand() -> bool { true }
+ }
+
+ #paw_impl
+ }
+}
+
+fn impl_structopt(input: &DeriveInput) -> TokenStream {
+ use syn::Data::*;
+
+ let struct_name = &input.ident;
+
+ set_dummy(quote! {
+ impl ::structopt::StructOpt for #struct_name {
+ fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> {
+ unimplemented!()
+ }
+ fn from_clap(_matches: &::structopt::clap::ArgMatches) -> Self {
+ unimplemented!()
+ }
+ }
+ });
+
+ match input.data {
+ Struct(DataStruct {
+ fields: syn::Fields::Named(ref fields),
+ ..
+ }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs),
+ Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs),
+ _ => abort_call_site!("structopt only supports non-tuple structs and enums"),
+ }
+}