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 { |
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 | |
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 | |
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 { |
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 { |
182 | fn to_tokens(&self, tokens: &mut TokenStream) { |
183 | match self { |
184 | Self::LitStr(t: &LitStr) => t.to_tokens(tokens), |
185 | Self::Expr(t: &Expr) => t.to_tokens(tokens), |
186 | Self::Call(t: &Vec) => { |
187 | let t: TokenStream = 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 { |
205 | 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 | |