1 use std::iter::FromIterator; 2 3 use proc_macro2::TokenStream; 4 use quote::quote; 5 use quote::ToTokens; 6 use syn::spanned::Spanned; 7 use syn::{ 8 parenthesized, 9 parse::{Parse, ParseStream}, 10 punctuated::Punctuated, 11 Attribute, Expr, Ident, LitStr, Token, 12 }; 13 14 use crate::utils::Sp; 15 16 #[derive(Clone)] 17 pub struct ClapAttr { 18 pub kind: Sp<AttrKind>, 19 pub name: Ident, 20 pub magic: Option<MagicAttrName>, 21 pub value: Option<AttrValue>, 22 } 23 24 impl ClapAttr { parse_all(all_attrs: &[Attribute]) -> Result<Vec<Self>, syn::Error>25 pub fn parse_all(all_attrs: &[Attribute]) -> Result<Vec<Self>, syn::Error> { 26 let mut parsed = Vec::new(); 27 for attr in all_attrs { 28 let kind = if attr.path().is_ident("clap") { 29 Sp::new(AttrKind::Clap, attr.path().span()) 30 } else if attr.path().is_ident("structopt") { 31 Sp::new(AttrKind::StructOpt, attr.path().span()) 32 } else if attr.path().is_ident("command") { 33 Sp::new(AttrKind::Command, attr.path().span()) 34 } else if attr.path().is_ident("group") { 35 Sp::new(AttrKind::Group, attr.path().span()) 36 } else if attr.path().is_ident("arg") { 37 Sp::new(AttrKind::Arg, attr.path().span()) 38 } else if attr.path().is_ident("value") { 39 Sp::new(AttrKind::Value, attr.path().span()) 40 } else { 41 continue; 42 }; 43 for mut attr in 44 attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated)? 45 { 46 attr.kind = kind; 47 parsed.push(attr); 48 } 49 } 50 Ok(parsed) 51 } 52 value_or_abort(&self) -> Result<&AttrValue, syn::Error>53 pub fn value_or_abort(&self) -> Result<&AttrValue, syn::Error> { 54 self.value 55 .as_ref() 56 .ok_or_else(|| format_err!(self.name, "attribute `{}` requires a value", self.name)) 57 } 58 lit_str_or_abort(&self) -> Result<&LitStr, syn::Error>59 pub fn lit_str_or_abort(&self) -> Result<&LitStr, syn::Error> { 60 let value = self.value_or_abort()?; 61 match value { 62 AttrValue::LitStr(tokens) => Ok(tokens), 63 AttrValue::Expr(_) | AttrValue::Call(_) => { 64 abort!( 65 self.name, 66 "attribute `{}` can only accept string literals", 67 self.name 68 ) 69 } 70 } 71 } 72 } 73 74 impl Parse for ClapAttr { parse(input: ParseStream) -> syn::Result<Self>75 fn parse(input: ParseStream) -> syn::Result<Self> { 76 let name: Ident = input.parse()?; 77 let name_str = name.to_string(); 78 79 let magic = match name_str.as_str() { 80 "rename_all" => Some(MagicAttrName::RenameAll), 81 "rename_all_env" => Some(MagicAttrName::RenameAllEnv), 82 "skip" => Some(MagicAttrName::Skip), 83 "next_display_order" => Some(MagicAttrName::NextDisplayOrder), 84 "next_help_heading" => Some(MagicAttrName::NextHelpHeading), 85 "default_value_t" => Some(MagicAttrName::DefaultValueT), 86 "default_values_t" => Some(MagicAttrName::DefaultValuesT), 87 "default_value_os_t" => Some(MagicAttrName::DefaultValueOsT), 88 "default_values_os_t" => Some(MagicAttrName::DefaultValuesOsT), 89 "long" => Some(MagicAttrName::Long), 90 "short" => Some(MagicAttrName::Short), 91 "value_parser" => Some(MagicAttrName::ValueParser), 92 "action" => Some(MagicAttrName::Action), 93 "env" => Some(MagicAttrName::Env), 94 "flatten" => Some(MagicAttrName::Flatten), 95 "value_enum" => Some(MagicAttrName::ValueEnum), 96 "from_global" => Some(MagicAttrName::FromGlobal), 97 "subcommand" => Some(MagicAttrName::Subcommand), 98 "external_subcommand" => Some(MagicAttrName::ExternalSubcommand), 99 "verbatim_doc_comment" => Some(MagicAttrName::VerbatimDocComment), 100 "about" => Some(MagicAttrName::About), 101 "long_about" => Some(MagicAttrName::LongAbout), 102 "long_help" => Some(MagicAttrName::LongHelp), 103 "author" => Some(MagicAttrName::Author), 104 "version" => Some(MagicAttrName::Version), 105 _ => None, 106 }; 107 108 let value = if input.peek(Token![=]) { 109 // `name = value` attributes. 110 let assign_token = input.parse::<Token![=]>()?; // skip '=' 111 if input.peek(LitStr) { 112 let lit: LitStr = input.parse()?; 113 Some(AttrValue::LitStr(lit)) 114 } else { 115 match input.parse::<Expr>() { 116 Ok(expr) => Some(AttrValue::Expr(expr)), 117 118 Err(_) => abort! { 119 assign_token, 120 "expected `string literal` or `expression` after `=`" 121 }, 122 } 123 } 124 } else if input.peek(syn::token::Paren) { 125 // `name(...)` attributes. 126 let nested; 127 parenthesized!(nested in input); 128 129 let method_args: Punctuated<_, _> = nested.parse_terminated(Expr::parse, Token![,])?; 130 Some(AttrValue::Call(Vec::from_iter(method_args))) 131 } else { 132 None 133 }; 134 135 Ok(Self { 136 kind: Sp::new(AttrKind::Clap, name.span()), 137 name, 138 magic, 139 value, 140 }) 141 } 142 } 143 144 #[derive(Copy, Clone, PartialEq, Eq)] 145 pub enum MagicAttrName { 146 Short, 147 Long, 148 ValueParser, 149 Action, 150 Env, 151 Flatten, 152 ValueEnum, 153 FromGlobal, 154 Subcommand, 155 VerbatimDocComment, 156 ExternalSubcommand, 157 About, 158 LongAbout, 159 LongHelp, 160 Author, 161 Version, 162 RenameAllEnv, 163 RenameAll, 164 Skip, 165 DefaultValueT, 166 DefaultValuesT, 167 DefaultValueOsT, 168 DefaultValuesOsT, 169 NextDisplayOrder, 170 NextHelpHeading, 171 } 172 173 #[derive(Clone)] 174 #[allow(clippy::large_enum_variant)] 175 pub enum AttrValue { 176 LitStr(LitStr), 177 Expr(Expr), 178 Call(Vec<Expr>), 179 } 180 181 impl ToTokens for AttrValue { to_tokens(&self, tokens: &mut TokenStream)182 fn to_tokens(&self, tokens: &mut TokenStream) { 183 match self { 184 Self::LitStr(t) => t.to_tokens(tokens), 185 Self::Expr(t) => t.to_tokens(tokens), 186 Self::Call(t) => { 187 let t = quote!(#(#t),*); 188 t.to_tokens(tokens) 189 } 190 } 191 } 192 } 193 194 #[derive(Copy, Clone, PartialEq, Eq)] 195 pub enum AttrKind { 196 Clap, 197 StructOpt, 198 Command, 199 Group, 200 Arg, 201 Value, 202 } 203 204 impl AttrKind { as_str(&self) -> &'static str205 pub fn as_str(&self) -> &'static str { 206 match self { 207 Self::Clap => "clap", 208 Self::StructOpt => "structopt", 209 Self::Command => "command", 210 Self::Group => "group", 211 Self::Arg => "arg", 212 Self::Value => "value", 213 } 214 } 215 } 216