use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{parse_macro_input, token, Attribute, Error, Meta, Path, Token};
enum Attr {
Configure {
hash: Token![#],
square_bracket: token::Bracket,
_path: Path,
meta: ConfigureMeta,
},
Other(Attribute),
}
struct CfgAttrsMeta(
)*`
Vec<Attr>,
);
struct ConfigureMeta {
condition: Meta,
comma: Token![,],
metas: Punctuated<Meta, Token![,]>,
}
fn parse_metas(input: ParseStream) -> syn::Result<Punctuated<Meta, Token![,]>> {
Ok(input
.parse_terminated(Attribute::parse_outer, Token![,])?
.into_iter()
.flatten()
.map(|attribute| attribute.meta)
.collect())
}
impl Parse for CfgAttrsMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attributes = input.call(Attribute::parse_outer)?;
let mut attrs = Vec::with_capacity(attributes.len());
for attribute in attributes {
let attr = if attribute.path().is_ident("configure") {
let (tokens, _path) = match attribute.meta {
Meta::List(list) => (list.tokens, list.path),
meta => {
return Err(Error::new(
meta.span(),
"expected attribute arguments in parentheses: configure(...)",
))
},
};
Attr::Configure {
hash: attribute.pound_token,
square_bracket: attribute.bracket_token,
_path,
meta: syn::parse2(tokens)?,
}
} else {
Attr::Other(attribute)
};
attrs.push(attr);
}
Ok(Self(attrs))
}
}
impl Parse for ConfigureMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
condition: input.parse()?,
comma: input.parse()?,
metas: input.call(parse_metas)?,
})
}
}
impl ToTokens for Attr {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
Self::Configure {
hash,
square_bracket,
meta,
..
} => {
hash.to_tokens(tokens);
square_bracket.surround(tokens, |tokens| quote!(cfg_attr(#meta)).to_tokens(tokens));
},
Self::Other(attr) => attr.to_tokens(tokens),
}
}
}
impl ToTokens for CfgAttrsMeta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self(attrs) = self;
for attr in attrs {
attr.to_tokens(tokens);
}
}
}
impl ToTokens for ConfigureMeta {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.condition.to_tokens(tokens);
self.comma.to_tokens(tokens);
self.metas.to_tokens(tokens);
}
}
#[proc_macro_attribute]
pub fn cfg_attrs(attr: TokenStream, item: TokenStream) -> TokenStream {
let cfg_attrs = parse_macro_input!(attr as CfgAttrsMeta);
let item: proc_macro2::TokenStream = item.into();
let tokens = quote! {
#cfg_attrs
#item
};
tokens.into()
}