bpaf_derive/
attrs.rs

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, with override, if specified
137    Short { name: Option<LitChar>, span: Span },
138    /// Long name, with override, if specified
139    Long { name: Option<LitStr>, span: Span },
140    /// Enum variable, name must be specified
141    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    /// Those items can change the type of the result
193    Parse(PostParse),
194    /// Those items can't change the type but can change the behavior
195    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    /// Names given with short, long and env
378    pub naming: Vec<Name>,
379
380    /// consumer attribute, derived
381    pub consumer: Vec<Consumer>,
382
383    /// post processing functions
384    pub postpr: Vec<Post>,
385
386    /// help specified by help(xxx)
387    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}