// Copyright 2018 Guillaume Pinot (@TeXitoi) , // Kevin Knapp (@kbknapp) , and // Ana Hobden (@hoverbear) // // 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 work was derived from Structopt (https://github.com/TeXitoi/structopt) // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the // MIT/Apache 2.0 license. use crate::{ parse::*, utils::{inner_type, is_simple_ty, process_doc_comment, Sp, Ty}, }; use std::env; use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{self, Span, TokenStream}; use proc_macro_error::abort; use quote::{quote, quote_spanned, ToTokens}; use syn::{ self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Ident, LitStr, MetaNameValue, Type, Variant, }; /// Default casing style for generated arguments. pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; /// Default casing style for environment variables pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake; #[derive(Clone)] pub struct Attrs { name: Name, casing: Sp, env_casing: Sp, ty: Option, doc_comment: Vec, methods: Vec, value_parser: Option, action: Option, parser: Option>, verbatim_doc_comment: Option, next_display_order: Option, next_help_heading: Option, help_heading: Option, is_enum: bool, is_positional: bool, kind: Sp, } impl Attrs { pub fn from_struct( span: Span, attrs: &[Attribute], name: Name, argument_casing: Sp, env_casing: Sp, ) -> Self { let mut res = Self::new(span, name, None, argument_casing, env_casing); res.push_attrs(attrs); res.push_doc_comment(attrs, "about"); if let Some(value_parser) = res.value_parser.as_ref() { abort!( value_parser.span(), "`value_parser` attribute is only allowed on fields" ); } if let Some(action) = res.action.as_ref() { abort!( action.span(), "`action` attribute is only allowed on fields" ); } if let Some(parser) = res.parser.as_ref() { abort!(parser.span(), "`parse` attribute is only allowed on fields"); } match &*res.kind { Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"), Kind::Arg(_) => res, Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), Kind::ExternalSubcommand => abort!( res.kind.span(), "external_subcommand is only allowed on fields" ), } } pub fn from_variant( variant: &Variant, struct_casing: Sp, env_casing: Sp, ) -> Self { let name = variant.ident.clone(); let mut res = Self::new( variant.span(), Name::Derived(name), None, struct_casing, env_casing, ); res.push_attrs(&variant.attrs); res.push_doc_comment(&variant.attrs, "about"); match &*res.kind { Kind::Flatten => { if let Some(value_parser) = res.value_parser.as_ref() { abort!( value_parser.span(), "`value_parser` attribute is not allowed for flattened entry" ); } if let Some(action) = res.action.as_ref() { abort!( action.span(), "`action` attribute is not allowed for flattened entry" ); } if let Some(parser) = res.parser.as_ref() { abort!( parser.span(), "parse attribute is not allowed for flattened entry" ); } if res.has_explicit_methods() { abort!( res.kind.span(), "methods are not allowed for flattened entry" ); } // ignore doc comments res.doc_comment = vec![]; } Kind::ExternalSubcommand => (), Kind::Subcommand(_) => { if let Some(value_parser) = res.value_parser.as_ref() { abort!( value_parser.span(), "`value_parser` attribute is not allowed for subcommand" ); } if let Some(action) = res.action.as_ref() { abort!( action.span(), "`action` attribute is not allowed for subcommand" ); } if let Some(parser) = res.parser.as_ref() { abort!( parser.span(), "parse attribute is not allowed for subcommand" ); } use syn::Fields::*; use syn::FieldsUnnamed; let field_ty = match variant.fields { Named(_) => { abort!(variant.span(), "structs are not allowed for subcommand"); } Unit => abort!(variant.span(), "unit-type is not allowed for subcommand"), Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { &unnamed[0].ty } Unnamed(..) => { abort!( variant, "non single-typed tuple is not allowed for subcommand" ) } }; let ty = Ty::from_syn_ty(field_ty); match *ty { Ty::OptionOption => { abort!( field_ty, "Option> type is not allowed for subcommand" ); } Ty::OptionVec => { abort!( field_ty, "Option> type is not allowed for subcommand" ); } _ => (), } res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span()); } Kind::Skip(_) => (), Kind::FromGlobal(_) => { abort!(res.kind.span(), "from_global is not supported on variants"); } Kind::Arg(_) => (), } res } pub fn from_value_enum_variant( variant: &Variant, argument_casing: Sp, env_casing: Sp, ) -> Self { let mut res = Self::new( variant.span(), Name::Derived(variant.ident.clone()), None, argument_casing, env_casing, ); res.push_attrs(&variant.attrs); res.push_doc_comment(&variant.attrs, "help"); if let Some(value_parser) = res.value_parser.as_ref() { abort!( value_parser.span(), "`value_parser` attribute is only allowed on fields" ); } if let Some(action) = res.action.as_ref() { abort!( action.span(), "`action` attribute is only allowed on fields" ); } if let Some(parser) = res.parser.as_ref() { abort!(parser.span(), "`parse` attribute is only allowed on fields"); } match &*res.kind { Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), Kind::Skip(_) => res, Kind::Arg(_) => res, Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), Kind::ExternalSubcommand => abort!( res.kind.span(), "external_subcommand is only allowed on fields" ), } } pub fn from_field( field: &Field, struct_casing: Sp, env_casing: Sp, ) -> Self { let name = field.ident.clone().unwrap(); let mut res = Self::new( field.span(), Name::Derived(name), Some(field.ty.clone()), struct_casing, env_casing, ); res.push_attrs(&field.attrs); res.push_doc_comment(&field.attrs, "help"); match &*res.kind { Kind::Flatten => { if let Some(value_parser) = res.value_parser.as_ref() { abort!( value_parser.span(), "`value_parser` attribute is not allowed for flattened entry" ); } if let Some(action) = res.action.as_ref() { abort!( action.span(), "`action` attribute is not allowed for flattened entry" ); } if let Some(parser) = res.parser.as_ref() { abort!( parser.span(), "parse attribute is not allowed for flattened entry" ); } if res.has_explicit_methods() { abort!( res.kind.span(), "methods are not allowed for flattened entry" ); } // ignore doc comments res.doc_comment = vec![]; } Kind::ExternalSubcommand => { abort! { res.kind.span(), "`external_subcommand` can be used only on enum variants" } } Kind::Subcommand(_) => { if let Some(value_parser) = res.value_parser.as_ref() { abort!( value_parser.span(), "`value_parser` attribute is not allowed for subcommand" ); } if let Some(action) = res.action.as_ref() { abort!( action.span(), "`action` attribute is not allowed for subcommand" ); } if let Some(parser) = res.parser.as_ref() { abort!( parser.span(), "parse attribute is not allowed for subcommand" ); } if res.has_explicit_methods() { abort!( res.kind.span(), "methods in attributes are not allowed for subcommand" ); } let ty = Ty::from_syn_ty(&field.ty); match *ty { Ty::OptionOption => { abort!( field.ty, "Option> type is not allowed for subcommand" ); } Ty::OptionVec => { abort!( field.ty, "Option> type is not allowed for subcommand" ); } _ => (), } res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span()); } Kind::Skip(_) => { if res.has_explicit_methods() { abort!( res.kind.span(), "methods are not allowed for skipped fields" ); } } Kind::FromGlobal(orig_ty) => { let ty = Ty::from_syn_ty(&field.ty); res.kind = Sp::new(Kind::FromGlobal(ty), orig_ty.span()); } Kind::Arg(_) => { let mut ty = Ty::from_syn_ty(&field.ty); if res.parser.is_some() { if let Some(value_parser) = res.value_parser.as_ref() { abort!( value_parser.span(), "`value_parser` attribute conflicts with `parse` attribute" ); } if let Some(action) = res.action.as_ref() { abort!( action.span(), "`action` attribute conflicts with `parse` attribute" ); } match *ty { Ty::Option | Ty::Vec | Ty::OptionVec => (), _ => ty = Sp::new(Ty::Other, ty.span()), } } match *ty { Ty::Option => { if let Some(m) = res.find_default_method() { abort!(m.name, "default_value is meaningless for Option") } } Ty::OptionOption => { if res.is_positional() { abort!( field.ty, "Option> type is meaningless for positional argument" ) } } Ty::OptionVec => { if res.is_positional() { abort!( field.ty, "Option> type is meaningless for positional argument" ) } } _ => (), } res.kind = Sp::new( Kind::Arg(ty), field .ident .as_ref() .map(|i| i.span()) .unwrap_or_else(|| field.ty.span()), ); } } res } fn new( default_span: Span, name: Name, ty: Option, casing: Sp, env_casing: Sp, ) -> Self { Self { name, ty, casing, env_casing, doc_comment: vec![], methods: vec![], value_parser: None, action: None, parser: None, verbatim_doc_comment: None, next_display_order: None, next_help_heading: None, help_heading: None, is_enum: false, is_positional: true, kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span), } } fn push_method(&mut self, name: Ident, arg: impl ToTokens) { if name == "name" || name == "id" { self.name = Name::Assigned(quote!(#arg)); } else if name == "value_parser" { self.value_parser = Some(ValueParser::Explicit(Method::new(name, quote!(#arg)))); } else if name == "action" { self.action = Some(Action::Explicit(Method::new(name, quote!(#arg)))); } else { if name == "short" || name == "long" { self.is_positional = false; } self.methods.push(Method::new(name, quote!(#arg))); } } fn push_attrs(&mut self, attrs: &[Attribute]) { use ClapAttr::*; let parsed = parse_clap_attributes(attrs); for attr in &parsed { let attr = attr.clone(); match attr { Short(ident) => { self.push_method(ident, self.name.clone().translate_char(*self.casing)); } Long(ident) => { self.push_method(ident, self.name.clone().translate(*self.casing)); } ValueParser(ident) => { use crate::attrs::ValueParser; self.value_parser = Some(ValueParser::Implicit(ident)); } Action(ident) => { use crate::attrs::Action; self.action = Some(Action::Implicit(ident)); } Env(ident) => { self.push_method(ident, self.name.clone().translate(*self.env_casing)); } ValueEnum(_) => self.is_enum = true, FromGlobal(ident) => { let ty = Sp::call_site(Ty::Other); let kind = Sp::new(Kind::FromGlobal(ty), ident.span()); self.set_kind(kind); } Subcommand(ident) => { let ty = Sp::call_site(Ty::Other); let kind = Sp::new(Kind::Subcommand(ty), ident.span()); self.set_kind(kind); } ExternalSubcommand(ident) => { let kind = Sp::new(Kind::ExternalSubcommand, ident.span()); self.set_kind(kind); } Flatten(ident) => { let kind = Sp::new(Kind::Flatten, ident.span()); self.set_kind(kind); } Skip(ident, expr) => { let kind = Sp::new(Kind::Skip(expr), ident.span()); self.set_kind(kind); } VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident), DefaultValueT(ident, expr) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( ident, "#[clap(default_value_t)] (without an argument) can be used \ only on field level"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; let val = if let Some(expr) = expr { quote!(#expr) } else { quote!(<#ty as ::std::default::Default>::default()) }; let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { quote_spanned!(ident.span()=> { { let val: #ty = #val; clap::ValueEnum::to_possible_value(&val).unwrap().get_name() } }) } else { quote_spanned!(ident.span()=> { static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy = clap::__macro_refs::once_cell::sync::Lazy::new(|| { let val: #ty = #val; ::std::string::ToString::to_string(&val) }); &*DEFAULT_VALUE }) }; let raw_ident = Ident::new("default_value", ident.span()); self.methods.push(Method::new(raw_ident, val)); } DefaultValuesT(ident, expr) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( ident, "#[clap(default_values_t)] (without an argument) can be used \ only on field level"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; let container_type = Ty::from_syn_ty(ty); if *container_type != Ty::Vec { abort!( ident, "#[clap(default_values_t)] can be used only on Vec types"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") } let inner_type = inner_type(ty); // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and // `Vec<#inner_type>`. let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { quote_spanned!(ident.span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> Vec<&'static str> where T: ::std::borrow::Borrow<#inner_type> { iterable .into_iter() .map(|val| { clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name() }) .collect() } static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy> = clap::__macro_refs::once_cell::sync::Lazy::new(|| { iter_to_vals(#expr) }); &*DEFAULT_VALUES.as_slice() } }) } else { quote_spanned!(ident.span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> Vec where T: ::std::borrow::Borrow<#inner_type> { iterable.into_iter().map(|val| val.borrow().to_string()).collect() } static DEFAULT_STRINGS: clap::__macro_refs::once_cell::sync::Lazy> = clap::__macro_refs::once_cell::sync::Lazy::new(|| { iter_to_vals(#expr) }); static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy> = clap::__macro_refs::once_cell::sync::Lazy::new(|| { DEFAULT_STRINGS.iter().map(::std::string::String::as_str).collect() }); &*DEFAULT_VALUES.as_slice() } }) }; self.methods .push(Method::new(Ident::new("default_values", ident.span()), val)); } DefaultValueOsT(ident, expr) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( ident, "#[clap(default_value_os_t)] (without an argument) can be used \ only on field level"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; let val = if let Some(expr) = expr { quote!(#expr) } else { quote!(<#ty as ::std::default::Default>::default()) }; let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { quote_spanned!(ident.span()=> { { let val: #ty = #val; clap::ValueEnum::to_possible_value(&val).unwrap().get_name() } }) } else { quote_spanned!(ident.span()=> { static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<::std::ffi::OsString> = clap::__macro_refs::once_cell::sync::Lazy::new(|| { let val: #ty = #val; ::std::ffi::OsString::from(val) }); &*DEFAULT_VALUE }) }; let raw_ident = Ident::new("default_value_os", ident.span()); self.methods.push(Method::new(raw_ident, val)); } DefaultValuesOsT(ident, expr) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( ident, "#[clap(default_values_os_t)] (without an argument) can be used \ only on field level"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; let container_type = Ty::from_syn_ty(ty); if *container_type != Ty::Vec { abort!( ident, "#[clap(default_values_os_t)] can be used only on Vec types"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") } let inner_type = inner_type(ty); // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and // `Vec<#inner_type>`. let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { quote_spanned!(ident.span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> Vec<&'static ::std::ffi::OsStr> where T: ::std::borrow::Borrow<#inner_type> { iterable .into_iter() .map(|val| { clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name() }) .map(::std::ffi::OsStr::new) .collect() } static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy> = clap::__macro_refs::once_cell::sync::Lazy::new(|| { iter_to_vals(#expr) }); &*DEFAULT_VALUES.as_slice() } }) } else { quote_spanned!(ident.span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> Vec<::std::ffi::OsString> where T: ::std::borrow::Borrow<#inner_type> { iterable.into_iter().map(|val| val.borrow().into()).collect() } static DEFAULT_OS_STRINGS: clap::__macro_refs::once_cell::sync::Lazy> = clap::__macro_refs::once_cell::sync::Lazy::new(|| { iter_to_vals(#expr) }); static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy> = clap::__macro_refs::once_cell::sync::Lazy::new(|| { DEFAULT_OS_STRINGS.iter().map(::std::ffi::OsString::as_os_str).collect() }); &*DEFAULT_VALUES.as_slice() } }) }; self.methods.push(Method::new( Ident::new("default_values_os", ident.span()), val, )); } NextDisplayOrder(ident, expr) => { self.next_display_order = Some(Method::new(ident, quote!(#expr))); } HelpHeading(ident, expr) => { self.help_heading = Some(Method::new(ident, quote!(#expr))); } NextHelpHeading(ident, expr) => { self.next_help_heading = Some(Method::new(ident, quote!(#expr))); } About(ident) => { if let Some(method) = Method::from_env(ident, "CARGO_PKG_DESCRIPTION") { self.methods.push(method); } } Author(ident) => { if let Some(method) = Method::from_env(ident, "CARGO_PKG_AUTHORS") { self.methods.push(method); } } Version(ident) => { if let Some(method) = Method::from_env(ident, "CARGO_PKG_VERSION") { self.methods.push(method); } } NameLitStr(name, lit) => { self.push_method(name, lit); } NameExpr(name, expr) => { self.push_method(name, expr); } MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)), RenameAll(_, casing_lit) => { self.casing = CasingStyle::from_lit(casing_lit); } RenameAllEnv(_, casing_lit) => { self.env_casing = CasingStyle::from_lit(casing_lit); } Parse(ident, spec) => { self.parser = Some(Parser::from_spec(ident, spec)); } } } } fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) { use syn::Lit::*; use syn::Meta::*; let comment_parts: Vec<_> = attrs .iter() .filter(|attr| attr.path.is_ident("doc")) .filter_map(|attr| { if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() { Some(s.value()) } else { // non #[doc = "..."] attributes are not our concern // we leave them for rustc to handle None } }) .collect(); self.doc_comment = process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none()); } fn set_kind(&mut self, kind: Sp) { if let Kind::Arg(_) = *self.kind { self.kind = kind; } else { abort!( kind.span(), "`subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together" ); } } pub fn find_method(&self, name: &str) -> Option<&Method> { self.methods.iter().find(|m| m.name == name) } pub fn find_default_method(&self) -> Option<&Method> { self.methods .iter() .find(|m| m.name == "default_value" || m.name == "default_value_os") } /// generate methods from attributes on top of struct or enum pub fn initial_top_level_methods(&self) -> TokenStream { let next_display_order = self.next_display_order.as_ref().into_iter(); let next_help_heading = self.next_help_heading.as_ref().into_iter(); let help_heading = self.help_heading.as_ref().into_iter(); quote!( #(#next_display_order)* #(#next_help_heading)* #(#help_heading)* ) } pub fn final_top_level_methods(&self) -> TokenStream { let methods = &self.methods; let doc_comment = &self.doc_comment; quote!( #(#doc_comment)* #(#methods)*) } /// generate methods on top of a field pub fn field_methods(&self, supports_long_help: bool) -> proc_macro2::TokenStream { let methods = &self.methods; let help_heading = self.help_heading.as_ref().into_iter(); match supports_long_help { true => { let doc_comment = &self.doc_comment; quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* ) } false => { let doc_comment = self .doc_comment .iter() .filter(|mth| mth.name != "long_help"); quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* ) } } } pub fn next_display_order(&self) -> TokenStream { let next_display_order = self.next_display_order.as_ref().into_iter(); quote!( #(#next_display_order)* ) } pub fn next_help_heading(&self) -> TokenStream { let next_help_heading = self.next_help_heading.as_ref().into_iter(); let help_heading = self.help_heading.as_ref().into_iter(); quote!( #(#next_help_heading)* #(#help_heading)* ) } #[cfg(feature = "unstable-v4")] pub fn id(&self) -> TokenStream { self.name.clone().raw() } #[cfg(not(feature = "unstable-v4"))] pub fn id(&self) -> TokenStream { self.cased_name() } pub fn cased_name(&self) -> TokenStream { self.name.clone().translate(*self.casing) } pub fn value_name(&self) -> TokenStream { self.name.clone().translate(CasingStyle::ScreamingSnake) } pub fn value_parser(&self, field_type: &Type) -> Method { self.value_parser .clone() .map(|p| { let inner_type = inner_type(field_type); p.resolve(inner_type) }) .unwrap_or_else(|| { if let Some(action) = self.action.as_ref() { let inner_type = inner_type(field_type); let span = action.span(); default_value_parser(inner_type, span) } else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) { self.parser(field_type).value_parser() } else { let inner_type = inner_type(field_type); let span = self .action .as_ref() .map(|a| a.span()) .unwrap_or_else(|| self.kind.span()); default_value_parser(inner_type, span) } }) } pub fn action(&self, field_type: &Type) -> Method { self.action .clone() .map(|p| p.resolve(field_type)) .unwrap_or_else(|| { if let Some(value_parser) = self.value_parser.as_ref() { let span = value_parser.span(); default_action(field_type, span) } else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) { self.parser(field_type).action() } else { let span = self .value_parser .as_ref() .map(|a| a.span()) .unwrap_or_else(|| self.kind.span()); default_action(field_type, span) } }) } #[cfg(feature = "unstable-v4")] pub fn ignore_parser(&self) -> bool { self.parser.is_none() } #[cfg(not(feature = "unstable-v4"))] pub fn ignore_parser(&self) -> bool { self.value_parser.is_some() || self.action.is_some() } pub fn explicit_parser(&self) -> bool { self.parser.is_some() } pub fn parser(&self, field_type: &Type) -> Sp { self.parser .clone() .unwrap_or_else(|| Parser::from_type(field_type, self.kind.span())) } pub fn kind(&self) -> Sp { self.kind.clone() } pub fn is_enum(&self) -> bool { self.is_enum } pub fn is_positional(&self) -> bool { self.is_positional } pub fn ignore_case(&self) -> TokenStream { let method = self.find_method("ignore_case"); if let Some(method) = method { method.args.clone() } else { quote! { false } } } pub fn casing(&self) -> Sp { self.casing.clone() } pub fn env_casing(&self) -> Sp { self.env_casing.clone() } pub fn has_explicit_methods(&self) -> bool { self.methods .iter() .any(|m| m.name != "help" && m.name != "long_help") } } #[derive(Clone)] enum ValueParser { Explicit(Method), Implicit(Ident), } impl ValueParser { fn resolve(self, inner_type: &Type) -> Method { match self { Self::Explicit(method) => method, Self::Implicit(ident) => default_value_parser(inner_type, ident.span()), } } fn span(&self) -> Span { match self { Self::Explicit(method) => method.name.span(), Self::Implicit(ident) => ident.span(), } } } fn default_value_parser(inner_type: &Type, span: Span) -> Method { let func = Ident::new("value_parser", span); Method::new( func, quote_spanned! { span=> clap::value_parser!(#inner_type) }, ) } #[derive(Clone)] pub enum Action { Explicit(Method), Implicit(Ident), } impl Action { pub fn resolve(self, field_type: &Type) -> Method { match self { Self::Explicit(method) => method, Self::Implicit(ident) => default_action(field_type, ident.span()), } } pub fn span(&self) -> Span { match self { Self::Explicit(method) => method.name.span(), Self::Implicit(ident) => ident.span(), } } } fn default_action(field_type: &Type, span: Span) -> Method { let ty = Ty::from_syn_ty(field_type); let args = match *ty { Ty::Vec | Ty::OptionVec => { quote_spanned! { span=> clap::ArgAction::Append } } Ty::Option | Ty::OptionOption => { quote_spanned! { span=> clap::ArgAction::Set } } _ => { if is_simple_ty(field_type, "bool") { quote_spanned! { span=> clap::ArgAction::SetTrue } } else { quote_spanned! { span=> clap::ArgAction::Set } } } }; let func = Ident::new("action", span); Method::new(func, args) } #[allow(clippy::large_enum_variant)] #[derive(Clone)] pub enum Kind { Arg(Sp), FromGlobal(Sp), Subcommand(Sp), Flatten, Skip(Option), ExternalSubcommand, } #[derive(Clone)] pub struct Method { name: Ident, args: TokenStream, } impl Method { pub fn new(name: Ident, args: TokenStream) -> Self { Method { name, args } } fn from_env(ident: Ident, env_var: &str) -> Option { let mut lit = match env::var(env_var) { Ok(val) => { if val.is_empty() { return None; } LitStr::new(&val, ident.span()) } Err(_) => { abort!(ident, "cannot derive `{}` from Cargo.toml", ident; note = "`{}` environment variable is not set", env_var; help = "use `{} = \"...\"` to set {} manually", ident, ident; ); } }; if ident == "author" { let edited = process_author_str(&lit.value()); lit = LitStr::new(&edited, lit.span()); } Some(Method::new(ident, quote!(#lit))) } pub(crate) fn args(&self) -> &TokenStream { &self.args } } impl ToTokens for Method { fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) { let Method { ref name, ref args } = self; let tokens = quote!( .#name(#args) ); tokens.to_tokens(ts); } } /// replace all `:` with `, ` when not inside the `<>` /// /// `"author1:author2:author3" => "author1, author2, author3"` /// `"author1 :author2" => "author1 , author2" fn process_author_str(author: &str) -> String { let mut res = String::with_capacity(author.len()); let mut inside_angle_braces = 0usize; for ch in author.chars() { if inside_angle_braces > 0 && ch == '>' { inside_angle_braces -= 1; res.push(ch); } else if ch == '<' { inside_angle_braces += 1; res.push(ch); } else if inside_angle_braces == 0 && ch == ':' { res.push_str(", "); } else { res.push(ch); } } res } #[derive(Clone)] pub struct Parser { pub kind: Sp, pub func: TokenStream, } impl Parser { fn from_type(field_type: &Type, span: Span) -> Sp { if is_simple_ty(field_type, "bool") { let kind = Sp::new(ParserKind::FromFlag, span); let func = quote_spanned!(span=> ::std::convert::From::from); Sp::new(Parser { kind, func }, span) } else { let kind = Sp::new(ParserKind::TryFromStr, span); let func = quote_spanned!(span=> ::std::str::FromStr::from_str); Sp::new(Parser { kind, func }, span) } } fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp { use self::ParserKind::*; let kind = match &*spec.kind.to_string() { "from_str" => FromStr, "try_from_str" => TryFromStr, "from_os_str" => FromOsStr, "try_from_os_str" => TryFromOsStr, "from_occurrences" => FromOccurrences, "from_flag" => FromFlag, s => abort!(spec.kind.span(), "unsupported parser `{}`", s), }; let func = match spec.parse_func { None => match kind { FromStr | FromOsStr => { quote_spanned!(spec.kind.span()=> ::std::convert::From::from) } TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str), TryFromOsStr => abort!( spec.kind.span(), "you must set parser for `try_from_os_str` explicitly" ), FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }), FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from), }, Some(func) => match func { Expr::Path(_) => quote!(#func), _ => abort!(func, "`parse` argument must be a function path"), }, }; let kind = Sp::new(kind, spec.kind.span()); let parser = Parser { kind, func }; Sp::new(parser, parse_ident.span()) } fn value_parser(&self) -> Method { let func = Ident::new("value_parser", self.kind.span()); match *self.kind { ParserKind::FromStr | ParserKind::TryFromStr => Method::new( func, quote_spanned! { self.kind.span()=> clap::builder::ValueParser::string()}, ), ParserKind::FromOsStr | ParserKind::TryFromOsStr => Method::new( func, quote_spanned! { self.kind.span()=> clap::builder::ValueParser::os_string()}, ), ParserKind::FromOccurrences => Method::new( func, quote_spanned! { self.kind.span()=> clap::value_parser!(u64)}, ), ParserKind::FromFlag => Method::new( func, quote_spanned! { self.kind.span()=> clap::builder::ValueParser::bool()}, ), } } fn action(&self) -> Method { let func = Ident::new("action", self.kind.span()); match *self.kind { ParserKind::FromStr | ParserKind::TryFromStr | ParserKind::FromOsStr | ParserKind::TryFromOsStr => Method::new( func, quote_spanned! { self.kind.span()=> clap::ArgAction::StoreValue}, ), ParserKind::FromOccurrences | ParserKind::FromFlag => Method::new( func, quote_spanned! { self.kind.span()=> clap::ArgAction::IncOccurrence}, ), } } } #[derive(Debug, PartialEq, Clone, Copy)] pub enum ParserKind { FromStr, TryFromStr, FromOsStr, TryFromOsStr, FromOccurrences, FromFlag, } /// Defines the casing for the attributes long representation. #[derive(Copy, Clone, Debug, PartialEq)] pub enum CasingStyle { /// Indicate word boundaries with uppercase letter, excluding the first word. Camel, /// Keep all letters lowercase and indicate word boundaries with hyphens. Kebab, /// Indicate word boundaries with uppercase letter, including the first word. Pascal, /// Keep all letters uppercase and indicate word boundaries with underscores. ScreamingSnake, /// Keep all letters lowercase and indicate word boundaries with underscores. Snake, /// Keep all letters lowercase and remove word boundaries. Lower, /// Keep all letters uppercase and remove word boundaries. Upper, /// Use the original attribute name defined in the code. Verbatim, } impl CasingStyle { fn from_lit(name: LitStr) -> Sp { use self::CasingStyle::*; let normalized = name.value().to_upper_camel_case().to_lowercase(); let cs = |kind| Sp::new(kind, name.span()); match normalized.as_ref() { "camel" | "camelcase" => cs(Camel), "kebab" | "kebabcase" => cs(Kebab), "pascal" | "pascalcase" => cs(Pascal), "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake), "snake" | "snakecase" => cs(Snake), "lower" | "lowercase" => cs(Lower), "upper" | "uppercase" => cs(Upper), "verbatim" | "verbatimcase" => cs(Verbatim), s => abort!(name, "unsupported casing: `{}`", s), } } } #[derive(Clone)] pub enum Name { Derived(Ident), Assigned(TokenStream), } impl Name { #[cfg(feature = "unstable-v4")] pub fn raw(self) -> TokenStream { match self { Name::Assigned(tokens) => tokens, Name::Derived(ident) => { let s = ident.unraw().to_string(); quote_spanned!(ident.span()=> #s) } } } pub fn translate(self, style: CasingStyle) -> TokenStream { use CasingStyle::*; match self { Name::Assigned(tokens) => tokens, Name::Derived(ident) => { let s = ident.unraw().to_string(); let s = match style { Pascal => s.to_upper_camel_case(), Kebab => s.to_kebab_case(), Camel => s.to_lower_camel_case(), ScreamingSnake => s.to_shouty_snake_case(), Snake => s.to_snake_case(), Lower => s.to_snake_case().replace('_', ""), Upper => s.to_shouty_snake_case().replace('_', ""), Verbatim => s, }; quote_spanned!(ident.span()=> #s) } } } pub fn translate_char(self, style: CasingStyle) -> TokenStream { use CasingStyle::*; match self { Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ), Name::Derived(ident) => { let s = ident.unraw().to_string(); let s = match style { Pascal => s.to_upper_camel_case(), Kebab => s.to_kebab_case(), Camel => s.to_lower_camel_case(), ScreamingSnake => s.to_shouty_snake_case(), Snake => s.to_snake_case(), Lower => s.to_snake_case(), Upper => s.to_shouty_snake_case(), Verbatim => s, }; let s = s.chars().next().unwrap(); quote_spanned!(ident.span()=> #s) } } } }