From 5e20a29b4fdc8a2d442d1093681b396dcb4b816b Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 7 Jan 2020 11:18:04 +0000 Subject: Add structopt dependency in version 0.3.7 This patch series replaces argparse with structopt in the argument handling code. As a first step, we need structopt as a dependency. Import subrepo structopt/:structopt at efbdda4753592e27bc430fb01f7b9650b2f3174d Import subrepo bitflags/:bitflags at 30668016aca6bd3b02c766e8347e0b4080d4c296 Import subrepo clap/:clap at 784524f7eb193e35f81082cc69454c8c21b948f7 Import subrepo heck/:heck at 093d56fbf001e1506e56dbfa38631d99b1066df1 Import subrepo proc-macro-error/:proc-macro-error at 6c4cfe79a622c5de8ae68557993542be46eacae2 Import subrepo proc-macro2/:proc-macro2 at d5d48eddca4566e5438e8a2cbed4a74e049544de Import subrepo quote/:quote at 727436c6c137b20f0f34dde5d8fda2679b9747ad Import subrepo rustversion/:rustversion at 0c5663313516263059ce9059ef81fc7a1cf655ca Import subrepo syn-mid/:syn-mid at 5d3d85414a9e6674e1857ec22a87b96e04a6851a Import subrepo syn/:syn at e87c27e87f6f4ef8919d0372bdb056d53ef0d8f3 Import subrepo textwrap/:textwrap at abcd618beae3f74841032aa5b53c1086b0a57ca2 Import subrepo unicode-segmentation/:unicode-segmentation at 637c9874c4fe0c205ff27787faf150a40295c6c3 Import subrepo unicode-width/:unicode-width at 3033826f8bf05e82724140a981d5941e48fce393 Import subrepo unicode-xid/:unicode-xid at 4baae9fffb156ba229665b972a9cd5991787ceb7 --- structopt/structopt-derive/src/lib.rs | 667 ++++++++++++++++++++++++++++++++++ 1 file changed, 667 insertions(+) create mode 100644 structopt/structopt-derive/src/lib.rs (limited to 'structopt/structopt-derive/src/lib.rs') 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) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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, + 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, 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, + 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; + ::augment_clap(app) + } + }; + + GenOutput { + tokens: augmented_tokens, + attrs: initial_clap_app_gen.attrs, + } +} + +fn gen_augment_clap(fields: &Punctuated, 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); + ::augment_clap(app) + } + }; + + GenOutput { + tokens, + attrs: initial_clap_app_gen.attrs, + } +} + +fn gen_augment_clap_enum( + variants: &Punctuated, + 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, + 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 { + 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 { + 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, + 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, + 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"), + } +} -- cgit v1.2.1