• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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