1use proc_macro2::{Span, TokenStream};
2use quote::{quote, ToTokens};
3use syn::{
4 parse::{Parse, ParseStream},
5 token, Attribute, Error, Expr, Ident, LitChar, LitStr, Path, Result, Type,
6};
7
8use crate::{
9 help::Help,
10 utils::{
11 doc_comment, parse_arg, parse_arg2, parse_expr, parse_lit_char, parse_lit_str,
12 parse_opt_metavar, to_kebab_case,
13 },
14};
15
16#[inline(never)]
17fn type_fish(input: ParseStream) -> Result<Option<Type>> {
18 Ok(if input.peek(token::Colon) {
19 input.parse::<token::Colon>()?;
20 input.parse::<token::Colon>()?;
21 input.parse::<token::Lt>()?;
22 let ty = input.parse::<Type>()?;
23 input.parse::<token::Gt>()?;
24 Some(ty)
25 } else {
26 None
27 })
28}
29
30pub struct TurboFish<'a>(pub &'a Type);
31
32impl ToTokens for TurboFish<'_> {
33 fn to_tokens(&self, tokens: &mut TokenStream) {
34 let ty = &self.0;
35 quote!(::<#ty>).to_tokens(tokens);
36 }
37}
38
39#[derive(Debug, Clone)]
40pub enum Consumer {
41 Switch {
42 span: Span,
43 },
44 Flag {
45 present: Expr,
46 absent: Expr,
47 span: Span,
48 },
49 ReqFlag {
50 present: Expr,
51 span: Span,
52 },
53 Any {
54 metavar: LitStr,
55 ty: Option<Type>,
56 check: Box<Expr>,
57 span: Span,
58 },
59 Argument {
60 metavar: Option<LitStr>,
61 ty: Option<Type>,
62 span: Span,
63 },
64 Positional {
65 metavar: Option<LitStr>,
66 ty: Option<Type>,
67 span: Span,
68 },
69 External {
70 ident: Option<Path>,
71 span: Span,
72 },
73 Pure {
74 expr: Expr,
75 span: Span,
76 },
77 PureWith {
78 expr: Expr,
79 span: Span,
80 },
81}
82
83impl Consumer {
84 pub fn span(&self) -> Span {
85 match self {
86 Consumer::Switch { span }
87 | Consumer::Flag { span, .. }
88 | Consumer::ReqFlag { span, .. }
89 | Consumer::Any { span, .. }
90 | Consumer::Argument { span, .. }
91 | Consumer::Positional { span, .. }
92 | Consumer::External { span, .. }
93 | Consumer::PureWith { span, .. }
94 | Consumer::Pure { span, .. } => *span,
95 }
96 }
97
98 pub(crate) fn help_placement(&self) -> HelpPlacement {
99 match self {
100 Consumer::Switch { .. }
101 | Consumer::Flag { .. }
102 | Consumer::ReqFlag { .. }
103 | Consumer::Argument { .. } => HelpPlacement::AtName,
104 Consumer::Any { .. } | Consumer::Positional { .. } => HelpPlacement::AtConsumer,
105 Consumer::External { .. } | Consumer::PureWith { .. } | Consumer::Pure { .. } => {
106 HelpPlacement::NotAvailable
107 }
108 }
109 }
110}
111
112pub(crate) enum HelpPlacement {
113 AtName,
114 AtConsumer,
115 NotAvailable,
116}
117
118impl Consumer {
119 pub(crate) fn needs_name(&self) -> bool {
120 match self {
121 Consumer::Switch { .. }
122 | Consumer::Flag { .. }
123 | Consumer::ReqFlag { .. }
124 | Consumer::Argument { .. } => true,
125 Consumer::Pure { .. }
126 | Consumer::PureWith { .. }
127 | Consumer::Positional { .. }
128 | Consumer::Any { .. }
129 | Consumer::External { .. } => false,
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
135pub(crate) enum Name {
136 Short { name: Option<LitChar>, span: Span },
138 Long { name: Option<LitStr>, span: Span },
140 Env { name: Box<Expr> },
142}
143
144impl StrictName {
145 pub(crate) fn from_name(name: Name, ident: &Option<Ident>) -> Result<Self> {
146 Ok(match name {
147 Name::Short {
148 name: Some(name), ..
149 } => Self::Short { name },
150 Name::Short { name: None, span } => match ident {
151 Some(name) => {
152 let derived_name = to_kebab_case(&name.to_string()).chars().next().unwrap();
153 Self::Short { name: LitChar::new(derived_name, span) }
154 }
155 None => return Err(Error::new(span, "Can't derive an explicit name for unnamed struct, try adding a name here like short('f')", ))
156 },
157 Name::Long {
158 name: Some(name), ..
159 } => StrictName::Long { name },
160 Name::Long { name: None, span } => match ident {
161 Some(name) => {
162 let derived_name = to_kebab_case(&name.to_string());
163 Self::Long{ name: LitStr::new(&derived_name, span) }
164 }
165 None => return Err(Error::new(span, "Can't derive an explicit name for unnamed struct, try adding a name here like long(\"arg\")", ))
166 },
167 Name::Env { name, .. } => Self::Env { name },
168 })
169 }
170}
171
172#[derive(Debug, Clone)]
173pub(crate) enum StrictName {
174 Short { name: LitChar },
175 Long { name: LitStr },
176 Env { name: Box<Expr> },
177}
178
179impl ToTokens for StrictName {
180 fn to_tokens(&self, tokens: &mut TokenStream) {
181 match self {
182 StrictName::Short { name } => quote!(short(#name)),
183 StrictName::Long { name } => quote!(long(#name)),
184 StrictName::Env { name } => quote!(env(#name)),
185 }
186 .to_tokens(tokens);
187 }
188}
189
190#[derive(Debug, Clone)]
191pub(crate) enum Post {
192 Parse(PostParse),
194 Decor(PostDecor),
196}
197
198impl ToTokens for Post {
199 fn to_tokens(&self, tokens: &mut TokenStream) {
200 match self {
201 Post::Parse(p) => p.to_tokens(tokens),
202 Post::Decor(p) => p.to_tokens(tokens),
203 }
204 }
205}
206
207impl ToTokens for PostParse {
208 fn to_tokens(&self, tokens: &mut TokenStream) {
209 match self {
210 PostParse::Adjacent { .. } => quote!(adjacent()),
211 PostParse::Catch { .. } => quote!(catch()),
212 PostParse::Many { .. } => quote!(many()),
213 PostParse::Collect { .. } => quote!(collect()),
214 PostParse::Count { .. } => quote!(count()),
215 PostParse::Some_ { msg, .. } => quote!(some(#msg)),
216 PostParse::Map { f, .. } => quote!(map(#f)),
217 PostParse::Optional { .. } => quote!(optional()),
218 PostParse::Parse { f, .. } => quote!(parse(#f)),
219 PostParse::Strict { .. } => quote!(strict()),
220 PostParse::NonStrict { .. } => quote!(non_strict()),
221 PostParse::Anywhere { .. } => quote!(anywhere()),
222 }
223 .to_tokens(tokens);
224 }
225}
226
227impl ToTokens for PostDecor {
228 fn to_tokens(&self, tokens: &mut TokenStream) {
229 match self {
230 PostDecor::Complete { f, .. } => quote!(complete(#f)),
231 PostDecor::CompleteGroup { group, .. } => quote!(group(#group)),
232 PostDecor::CompleteShell { f, .. } => quote!(complete_shell(#f)),
233 PostDecor::DebugFallback { .. } => quote!(debug_fallback()),
234 PostDecor::DisplayFallback { .. } => quote!(display_fallback()),
235 PostDecor::FormatFallback { formatter, .. } => quote!(format_fallback(#formatter)),
236 PostDecor::Fallback { value, .. } => quote!(fallback(#value)),
237 PostDecor::FallbackWith { f, .. } => quote!(fallback_with(#f)),
238 PostDecor::Last { .. } => quote!(last()),
239 PostDecor::GroupHelp { doc, .. } => quote!(group_help(#doc)),
240 PostDecor::Guard { check, msg, .. } => quote!(guard(#check, #msg)),
241 PostDecor::Hide { .. } => quote!(hide()),
242 PostDecor::CustomUsage { usage, .. } => quote!(custom_usage(#usage)),
243 PostDecor::HideUsage { .. } => quote!(hide_usage()),
244 }
245 .to_tokens(tokens);
246 }
247}
248
249#[derive(Debug, Clone)]
250pub(crate) enum PostParse {
251 Adjacent { span: Span },
252 Catch { span: Span },
253 Many { span: Span },
254 Collect { span: Span },
255 Count { span: Span },
256 Some_ { span: Span, msg: Box<Expr> },
257 Map { span: Span, f: Box<Expr> },
258 Optional { span: Span },
259 Parse { span: Span, f: Box<Expr> },
260 Strict { span: Span },
261 NonStrict { span: Span },
262 Anywhere { span: Span },
263}
264impl PostParse {
265 fn span(&self) -> Span {
266 match self {
267 Self::Adjacent { span }
268 | Self::Catch { span }
269 | Self::Many { span }
270 | Self::Collect { span }
271 | Self::Count { span }
272 | Self::Some_ { span, .. }
273 | Self::Map { span, .. }
274 | Self::Optional { span }
275 | Self::Parse { span, .. }
276 | Self::Strict { span }
277 | Self::NonStrict { span }
278 | Self::Anywhere { span } => *span,
279 }
280 }
281}
282
283#[derive(Debug, Clone)]
284pub(crate) enum PostDecor {
285 Complete {
286 span: Span,
287 f: Box<Expr>,
288 },
289 CompleteGroup {
290 span: Span,
291 group: LitStr,
292 },
293 CompleteShell {
294 span: Span,
295 f: Box<Expr>,
296 },
297 DebugFallback {
298 span: Span,
299 },
300 DisplayFallback {
301 span: Span,
302 },
303 FormatFallback {
304 span: Span,
305 formatter: Box<Expr>,
306 },
307 Fallback {
308 span: Span,
309 value: Box<Expr>,
310 },
311 FallbackWith {
312 span: Span,
313 f: Box<Expr>,
314 },
315 Last {
316 span: Span,
317 },
318 GroupHelp {
319 span: Span,
320 doc: Box<Expr>,
321 },
322 Guard {
323 span: Span,
324 check: Box<Expr>,
325 msg: Box<Expr>,
326 },
327 Hide {
328 span: Span,
329 },
330 CustomUsage {
331 usage: Box<Expr>,
332 span: Span,
333 },
334 HideUsage {
335 span: Span,
336 },
337}
338impl PostDecor {
339 fn span(&self) -> Span {
340 match self {
341 Self::Complete { span, .. }
342 | Self::CompleteGroup { span, .. }
343 | Self::CompleteShell { span, .. }
344 | Self::DebugFallback { span }
345 | Self::DisplayFallback { span }
346 | Self::FormatFallback { span, .. }
347 | Self::Fallback { span, .. }
348 | Self::Last { span }
349 | Self::FallbackWith { span, .. }
350 | Self::GroupHelp { span, .. }
351 | Self::Guard { span, .. }
352 | Self::Hide { span }
353 | Self::CustomUsage { span, .. }
354 | Self::HideUsage { span } => *span,
355 }
356 }
357}
358
359impl Post {
360 pub fn can_derive(&self) -> bool {
361 match self {
362 Post::Parse(_) => false,
363 Post::Decor(_) => true,
364 }
365 }
366
367 pub fn span(&self) -> Span {
368 match self {
369 Post::Parse(p) => p.span(),
370 Post::Decor(d) => d.span(),
371 }
372 }
373}
374
375#[derive(Default, Debug)]
376pub(crate) struct FieldAttrs {
377 pub naming: Vec<Name>,
379
380 pub consumer: Vec<Consumer>,
382
383 pub postpr: Vec<Post>,
385
386 pub help: Vec<CustomHelp>,
388
389 pub(crate) ignore_rustdoc: bool,
390}
391
392impl Name {
393 pub(crate) fn parse(input: ParseStream, kw: &Ident) -> Result<Option<Self>> {
394 let span = kw.span();
395 Ok(Some(if kw == "short" {
396 let name = if input.peek(token::Paren) {
397 Some(parse_lit_char(input)?)
398 } else {
399 None
400 };
401 Name::Short { name, span }
402 } else if kw == "long" {
403 let name = if input.peek(token::Paren) {
404 Some(parse_lit_str(input)?)
405 } else {
406 None
407 };
408 Name::Long { name, span }
409 } else if kw == "env" {
410 let name = parse_expr(input)?;
411 Name::Env { name }
412 } else {
413 return Ok(None);
414 }))
415 }
416}
417
418impl Consumer {
419 fn parse(input: ParseStream, kw: &Ident) -> Result<Option<Self>> {
420 let span = kw.span();
421 Ok(Some(if kw == "argument" {
422 let ty = type_fish(input)?;
423 let metavar = parse_opt_metavar(input)?;
424 Consumer::Argument { metavar, ty, span }
425 } else if kw == "positional" {
426 let ty = type_fish(input)?;
427 let metavar = parse_opt_metavar(input)?;
428 Consumer::Positional { metavar, ty, span }
429 } else if kw == "any" {
430 let ty = type_fish(input)?;
431 let (metavar, check) = parse_arg2(input)?;
432 Consumer::Any {
433 metavar,
434 ty,
435 check,
436 span,
437 }
438 } else if kw == "switch" {
439 Consumer::Switch { span }
440 } else if kw == "flag" {
441 let (present, absent) = parse_arg2(input)?;
442 Consumer::Flag {
443 present,
444 absent,
445 span,
446 }
447 } else if kw == "req_flag" {
448 let present = parse_arg(input)?;
449 Consumer::ReqFlag { present, span }
450 } else if kw == "external" {
451 let ident = if input.peek(token::Paren) {
452 Some(parse_arg(input)?)
453 } else {
454 None
455 };
456 Consumer::External { ident, span }
457 } else if kw == "pure" {
458 let expr = parse_arg(input)?;
459 Consumer::Pure { expr, span }
460 } else if kw == "pure_with" {
461 let expr = parse_arg(input)?;
462 Consumer::PureWith { expr, span }
463 } else {
464 return Ok(None);
465 }))
466 }
467}
468
469impl PostParse {
470 pub(crate) fn parse(input: ParseStream, kw: &Ident) -> Result<Option<Self>> {
471 let span = kw.span();
472 Ok(Some(if kw == "adjacent" {
473 Self::Adjacent { span }
474 } else if kw == "catch" {
475 Self::Catch { span }
476 } else if kw == "many" {
477 Self::Many { span }
478 } else if kw == "collect" {
479 Self::Collect { span }
480 } else if kw == "count" {
481 Self::Count { span }
482 } else if kw == "map" {
483 let f = parse_arg(input)?;
484 Self::Map { span, f }
485 } else if kw == "optional" {
486 Self::Optional { span }
487 } else if kw == "parse" {
488 let f = parse_arg(input)?;
489 Self::Parse { span, f }
490 } else if kw == "strict" {
491 Self::Strict { span }
492 } else if kw == "non_strict" {
493 Self::NonStrict { span }
494 } else if kw == "some" {
495 let msg = parse_arg(input)?;
496 Self::Some_ { span, msg }
497 } else if kw == "anywhere" {
498 Self::Anywhere { span }
499 } else {
500 return Ok(None);
501 }))
502 }
503}
504
505impl PostDecor {
506 pub(crate) fn parse(input: ParseStream, kw: &Ident) -> Result<Option<Self>> {
507 let span = kw.span();
508 Ok(Some(if kw == "complete" {
509 let f = parse_arg(input)?;
510 Self::Complete { span, f }
511 } else if kw == "group" {
512 let group = parse_lit_str(input)?;
513 Self::CompleteGroup { span, group }
514 } else if kw == "complete_shell" {
515 let f = parse_arg(input)?;
516 Self::CompleteShell { span, f }
517 } else if kw == "debug_fallback" {
518 Self::DebugFallback { span }
519 } else if kw == "display_fallback" {
520 Self::DisplayFallback { span }
521 } else if kw == "format_fallback" {
522 let formatter = parse_expr(input)?;
523 Self::FormatFallback { span, formatter }
524 } else if kw == "fallback" {
525 let value = parse_expr(input)?;
526 Self::Fallback { span, value }
527 } else if kw == "last" {
528 Self::Last { span }
529 } else if kw == "fallback_with" {
530 let f = parse_expr(input)?;
531 Self::FallbackWith { span, f }
532 } else if kw == "group_help" {
533 let doc = parse_expr(input)?;
534 Self::GroupHelp { span, doc }
535 } else if kw == "guard" {
536 let (check, msg) = parse_arg2(input)?;
537 Self::Guard { span, check, msg }
538 } else if kw == "hide" {
539 Self::Hide { span }
540 } else if kw == "hide_usage" {
541 Self::HideUsage { span }
542 } else if kw == "custom_usage" {
543 let usage = parse_arg(input)?;
544 Self::CustomUsage { usage, span }
545 } else {
546 return Ok(None);
547 }))
548 }
549}
550
551#[derive(Debug)]
552pub(crate) struct CustomHelp {
553 pub span: Span,
554 pub doc: Box<Expr>,
555}
556
557#[derive(Debug, Clone)]
558pub(crate) struct EnumPrefix(pub Ident);
559
560impl ToTokens for EnumPrefix {
561 fn to_tokens(&self, tokens: &mut TokenStream) {
562 let name = &self.0;
563 quote!(#name ::).to_tokens(tokens);
564 }
565}
566
567impl CustomHelp {
568 fn parse(input: ParseStream, kw: &Ident) -> Result<Option<Self>> {
569 let span = kw.span();
570 Ok(if kw == "help" {
571 let doc = parse_arg(input)?;
572 Some(CustomHelp { span, doc })
573 } else {
574 None
575 })
576 }
577
578 fn span(&self) -> Span {
579 self.span
580 }
581}
582
583impl Parse for FieldAttrs {
584 fn parse(input: ParseStream) -> Result<Self> {
585 let mut res = FieldAttrs::default();
586 loop {
587 let fork = input.fork();
588 let kw = input.parse::<Ident>()?;
589 if kw == "ignore_rustdoc" {
590 res.ignore_rustdoc = true;
591 } else if let Some(name) = Name::parse(input, &kw)? {
592 res.naming.push(name);
593 } else if let Some(cons) = Consumer::parse(input, &kw)? {
594 res.consumer.push(cons);
595 } else if let Some(pp) = PostParse::parse(input, &kw)? {
596 res.postpr.push(Post::Parse(pp));
597 } else if let Some(pp) = PostDecor::parse(input, &kw)? {
598 res.postpr.push(Post::Decor(pp));
599 } else if let Some(help) = CustomHelp::parse(input, &kw)? {
600 res.help.push(help);
601 } else {
602 return Err(fork.error("Unexpected attribute in field annotation"));
603 }
604
605 if input.is_empty() {
606 break;
607 }
608 input.parse::<token::Comma>()?;
609 if input.is_empty() {
610 break;
611 }
612 }
613 res.validate()?;
614 Ok(res)
615 }
616}
617
618impl FieldAttrs {
619 fn validate(&self) -> Result<()> {
620 if self.consumer.len() > 1 {
621 return Err(Error::new(
622 self.consumer[1].span(),
623 "Structure annotation can have only one consumer attribute",
624 ));
625 }
626
627 if self.help.len() > 1 {
628 return Err(Error::new(
629 self.help[1].span(),
630 "Structure annotation can have only one help attribute",
631 ));
632 }
633
634 Ok(())
635 }
636}
637
638pub(crate) fn parse_bpaf_doc_attrs<T>(attrs: &[Attribute]) -> Result<(Option<T>, Option<Help>)>
639where
640 T: Parse,
641{
642 let mut help = Vec::new();
643 let mut parsed = None;
644
645 for attr in attrs {
646 if attr.path().is_ident("doc") {
647 if let Some(doc) = doc_comment(attr) {
648 help.push(doc);
649 }
650 } else if attr.path().is_ident("bpaf") {
651 parsed = Some(attr.parse_args::<T>()?);
652 }
653 }
654
655 let help = if help.is_empty() {
656 None
657 } else {
658 Some(Help::Doc(help.join("\n")))
659 };
660
661 Ok((parsed, help))
662}