1use std::iter::FromIterator;
2
3use proc_macro2::TokenStream;
4use quote::quote;
5use quote::ToTokens;
6use syn::spanned::Spanned;
7use syn::{
8 parenthesized,
9 parse::{Parse, ParseStream},
10 punctuated::Punctuated,
11 Attribute, Expr, Ident, LitStr, Token,
12};
13
14use crate::utils::Sp;
15
16#[derive(Clone)]
17pub struct ClapAttr {
18 pub kind: Sp<AttrKind>,
19 pub name: Ident,
20 pub magic: Option<MagicAttrName>,
21 pub value: Option<AttrValue>,
22}
23
24impl 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
74impl 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)]
145pub 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)]
175pub enum AttrValue {
176 LitStr(LitStr),
177 Expr(Expr),
178 Call(Vec<Expr>),
179}
180
181impl 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)]
195pub enum AttrKind {
196 Clap,
197 StructOpt,
198 Command,
199 Group,
200 Arg,
201 Value,
202}
203
204impl 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