clap_derive/
attr.rs

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(crate) struct ClapAttr {
18    pub(crate) kind: Sp<AttrKind>,
19    pub(crate) name: Ident,
20    pub(crate) magic: Option<MagicAttrName>,
21    pub(crate) value: Option<AttrValue>,
22}
23
24impl ClapAttr {
25    pub(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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) => t.to_tokens(tokens),
185            Self::Expr(t) => t.to_tokens(tokens),
186            Self::Call(t) => {
187                let t = quote!(#(#t),*);
188                t.to_tokens(tokens);
189            }
190        }
191    }
192}
193
194#[derive(Copy, Clone, PartialEq, Eq)]
195pub(crate) enum AttrKind {
196    Clap,
197    StructOpt,
198    Command,
199    Group,
200    Arg,
201    Value,
202}
203
204impl AttrKind {
205    pub(crate) 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}