1use proc_macro2::{Span, TokenStream};
2use quote::{quote, ToTokens};
3use syn::{
4 parse::ParseStream, parse_quote, spanned::Spanned, token, Attribute, Error, Ident, LitStr,
5 Result, Type, Visibility,
6};
7
8use crate::{
9 attrs::{
10 parse_bpaf_doc_attrs, Consumer, FieldAttrs, HelpPlacement, Name, Post, PostParse,
11 StrictName, TurboFish,
12 },
13 field::{split_type, Shape},
14 help::Help,
15 utils::to_snake_case,
16};
17
18#[derive(Debug, Clone)]
19pub(crate) struct StructField {
20 pub name: Option<Ident>,
21 pub env: Vec<StrictName>,
22 pub naming: Vec<StrictName>,
23 pub cons: Consumer,
24 pub postpr: Vec<Post>,
25 pub help: Option<Help>,
26}
27
28fn derive_consumer(name_present: bool, ty: &Type) -> Result<Consumer> {
29 let span = ty.span();
30 Ok(match split_type(ty) {
31 Shape::Bool => {
32 if name_present {
33 Consumer::Switch { span }
34 } else {
35 let msg = "Refusing to derive a positional item for bool, you can fix this by either adding a short/long name or making it positional explicitly";
36 return Err(Error::new(ty.span(), msg));
37 }
38 }
39 Shape::Unit => {
40 if name_present {
41 Consumer::ReqFlag {
42 present: parse_quote!(()),
43 span,
44 }
45 } else {
46 let msg = "Refusing to derive a positional item for (), you can fix this by either adding a short/long name or making it positional explicitly";
47 return Err(Error::new(ty.span(), msg));
48 }
49 }
50 Shape::Optional(t) | Shape::Multiple(t) | Shape::Direct(t) => {
51 let ty = Some(t);
52 let metavar = None;
53 if name_present {
54 Consumer::Argument { metavar, ty, span }
55 } else {
56 Consumer::Positional { metavar, ty, span }
57 }
58 }
59 })
60}
61
62struct MMetavar<'a>(Option<&'a LitStr>);
63impl ToTokens for MMetavar<'_> {
64 fn to_tokens(&self, tokens: &mut TokenStream) {
65 match &self.0 {
66 Some(mv) => mv.to_tokens(tokens),
67 None => quote!("ARG").to_tokens(tokens),
68 }
69 }
70}
71
72impl ToTokens for Consumer {
73 fn to_tokens(&self, tokens: &mut TokenStream) {
74 match self {
75 Consumer::Switch { .. } => quote!(switch()),
76 Consumer::Flag {
77 present, absent, ..
78 } => quote!(flag(#present, #absent)),
79 Consumer::ReqFlag { present, .. } => quote!(req_flag(#present)),
80 Consumer::Any {
81 metavar, ty, check, ..
82 } => match ty {
83 Some(ty) => quote!(::bpaf::any::<#ty, _, _>(#metavar, #check)),
84 None => quote!(::bpaf::any(#metavar, #check)),
85 },
86 Consumer::Argument { metavar, ty, .. } => {
87 let metavar = MMetavar(metavar.as_ref());
88 let tf = ty.as_ref().map(TurboFish);
89 quote!(argument #tf(#metavar))
90 }
91 Consumer::Positional { metavar, ty, .. } => {
92 let metavar = MMetavar(metavar.as_ref());
93 let tf = ty.as_ref().map(TurboFish);
94 quote!(::bpaf::positional #tf(#metavar))
95 }
96 Consumer::External { ident, .. } => {
97 quote!(#ident())
98 }
99 Consumer::Pure { expr, .. } => {
100 quote!(::bpaf::pure(#expr))
101 }
102 Consumer::PureWith { expr, .. } => {
103 quote!(::bpaf::pure_with(#expr))
104 }
105 }
106 .to_tokens(tokens);
107 }
108}
109
110impl ToTokens for StructField {
111 fn to_tokens(&self, tokens: &mut TokenStream) {
112 let StructField {
113 name: _,
114 env,
115 naming,
116 cons,
117 postpr,
118 help,
119 } = self;
120
121 let names = naming.iter().chain(env.iter());
122
123 let prefix = if cons.needs_name() {
124 quote!(::bpaf::)
125 } else {
126 quote!()
127 };
128
129 let help = help.iter();
130
131 match cons.help_placement() {
132 HelpPlacement::AtName => {
133 quote!(#prefix #( #names .)* #(help(#help).)* #cons #(.#postpr)*)
134 }
135 HelpPlacement::AtConsumer => {
136 quote!(#prefix #( #names .)* #cons #(.help(#help))* #(.#postpr)*)
137 }
138 HelpPlacement::NotAvailable => quote!(#prefix #(#names.)* #cons #(.#postpr)*),
139 }
140 .to_tokens(tokens);
141 }
142}
143
144impl StructField {
145 pub fn var_name(&self, ix: usize) -> Ident {
146 let name = &self.name;
147 match name {
148 Some(name) => name.clone(),
149 None => Ident::new(&format!("f{}", ix), Span::call_site()),
150 }
151 }
152
153 pub fn parse_named(input: ParseStream) -> Result<Self> {
154 let attrs = input.call(Attribute::parse_outer)?;
155 let _vis = input.parse::<Visibility>()?;
156 let name = input.parse::<Ident>()?;
157 input.parse::<token::Colon>()?;
158 let ty = input.parse::<Type>()?;
159 Self::make(Some(name), ty, &attrs)
160 }
161
162 pub fn parse_unnamed(input: ParseStream) -> Result<Self> {
163 let attrs = input.call(Attribute::parse_outer)?;
164 let _vis = input.parse::<Visibility>()?;
165 let ty = input.parse::<Type>()?;
166 Self::make(None, ty, &attrs)
167 }
168
169 #[allow(clippy::too_many_lines)]
170 pub(crate) fn make(name: Option<Ident>, ty: Type, attrs: &[Attribute]) -> Result<Self> {
171 let (field_attrs, mut help) = parse_bpaf_doc_attrs::<FieldAttrs>(attrs)?;
172
173 let mut field_attrs = field_attrs.unwrap_or_default();
174
175 if field_attrs.ignore_rustdoc {
176 help = None;
177 }
178
179 let derived_consumer = field_attrs.consumer.is_empty();
180
181 let mut cons = match field_attrs.consumer.pop() {
182 Some(cons) => cons,
183 None => derive_consumer(name.is_some() || !field_attrs.naming.is_empty(), &ty)?,
184 };
185
186 if let Consumer::External { span, ident: None } = &cons {
187 let span = *span;
188 match name.as_ref() {
189 Some(n) => {
190 let ident = Ident::new(&to_snake_case(&n.to_string()), n.span());
191 cons = Consumer::External {
192 span,
193 ident: Some(ident.into()),
194 };
195 }
196 None => {
197 return Err(Error::new(
198 span,
199 "Can't derive name for this external, try specifying one",
200 ))
201 }
202 }
203 }
204
205 let mut env = Vec::new();
206 let mut naming = Vec::new();
207 for attr in field_attrs.naming {
208 if let Name::Env { name, .. } = attr {
209 env.push(StrictName::Env { name });
210 } else {
211 naming.push(StrictName::from_name(attr, &name)?);
212 }
213 }
214
215 match (cons.needs_name(), !naming.is_empty()) {
216 (true, true) |
218 (false, false) => {}
220
221 (true, false) => match &name {
223 Some(n) => {
224 let span = n.span();
225 if n.to_string().chars().count() == 1 {
226 let short = Name::Short { name: None, span };
227 naming.push(StrictName::from_name(short, &name)?);
228 } else {
229 let long = Name::Long { name: None, span };
230 naming.push(StrictName::from_name(long, &name)?);
231 }
232 }
233 None => {
234 return Err(Error::new(
235 cons.span(),
236 "This consumer needs a name, you can specify it with long(\"name\") or short('n')",
237 ));
238 }
239 },
240
241 (false, true) => {
243 return Err(Error::new_spanned(
244 ty,
245 "field doesn't take a name annotation",
246 ));
247 }
248
249 };
250
251 let mut postpr = std::mem::take(&mut field_attrs.postpr);
252
253 let shape = split_type(&ty);
254
255 if let Consumer::Argument { ty, .. }
256 | Consumer::Positional { ty, .. }
257 | Consumer::Any { ty, .. } = &mut cons
258 {
259 if ty.is_none() {
260 match &shape {
261 Shape::Optional(t) | Shape::Multiple(t) | Shape::Direct(t) => {
262 *ty = Some(t.clone());
263 }
264 _ => {}
265 }
266 }
267 }
268
269 if derived_consumer {
270 for pp in &postpr {
271 if !pp.can_derive() {
272 let err = Error::new(
273 pp.span(),
274 "Can't derive implicit consumer with this annotation present",
275 );
276 return Err(err);
277 }
278 }
279 }
280 let span = ty.span();
281
282 if !(postpr.iter().any(|p| matches!(p, Post::Parse(_)))
283 || matches!(cons, Consumer::External { .. } | Consumer::Pure { .. }))
284 {
285 match shape {
286 Shape::Optional(_) => postpr.insert(0, Post::Parse(PostParse::Optional { span })),
287 Shape::Multiple(_) => postpr.insert(0, Post::Parse(PostParse::Many { span })),
288 Shape::Bool => {
289 if name.is_none()
290 && naming.is_empty()
291 && matches!(cons, Consumer::Switch { .. })
292 {
293 let msg = "Can't derive consumer for unnamed boolean field, try adding one of #[bpaf(positional)], #[bpaf(long(\"name\")] or #[bpaf(short('n'))] annotations to it";
294 let err = Error::new_spanned(ty, msg);
295 return Err(err);
296 }
297 }
298 Shape::Unit | Shape::Direct(_) => {}
299 }
300 }
301
302 let help = match field_attrs.help.pop() {
303 Some(h) => Some(Help::Custom(h.doc)),
304 None => help,
305 };
306
307 Ok(StructField {
308 name,
309 env,
310 naming,
311 cons,
312 postpr,
313 help,
314 })
315 }
316}