extern crate proc_macro;
extern crate proc_macro2;
use proc_macro2::{Punct, Spacing, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use std::str::FromStr;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{parse_macro_input, AttributeArgs, Ident, Lit, Meta, NestedMeta, Token};
mod macro_rules;
mod replace_macro_invocs;
use macro_rules::*;
use replace_macro_invocs::replace_macro_invocs;
#[proc_macro_attribute]
pub fn clean_docs(
args: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = parse_macro_input!(args as AttributeArgs);
let mac_rules = parse_macro_input!(item as MacroRules);
clean_docs_impl(args, mac_rules).into()
}
fn clean_docs_impl(args: AttributeArgs, mut mac_rules: MacroRules) -> TokenStream {
let mut priv_marker: Option<TokenStream> = None;
let mut priv_ident: Option<Ident> = None;
for arg in args {
if let NestedMeta::Meta(Meta::NameValue(arg)) = arg {
match arg
.path
.get_ident()
.map(Ident::to_string)
.as_ref()
.map(String::as_str)
{
Some("impl") => {
if let Lit::Str(val) = &arg.lit {
priv_marker = Some({
if let Ok(priv_marker) = TokenStream::from_str(&val.value()) {
priv_marker
} else {
return quote_spanned! {
arg.lit.span()=> compile_error!("invalid tokens");
};
}
})
} else {
return quote_spanned! {
arg.lit.span()=> compile_error!("expected string");
};
}
}
Some("internal") => {
if let Lit::Str(val) = &arg.lit {
priv_ident = Some({
if let Ok(priv_ident) = val.parse() {
priv_ident
} else {
return quote_spanned! {
arg.lit.span()=> compile_error!("expected identifier");
};
}
})
} else {
return quote_spanned! {
arg.lit.span()=> compile_error!("expected string");
};
}
}
_ => {
let arg_path = &arg.path;
let arg_str = quote!(#arg_path).to_string();
return quote_spanned! {
arg.span()=> compile_error!(concat!("invalid argument: ", #arg_str));
};
}
};
} else {
let arg_str = quote!(#arg).to_string();
return quote_spanned! {
arg.span()=> compile_error!(concat!("invalid argument: ", #arg_str));
};
}
}
let mut original = mac_rules.clone();
let pub_ident = &mac_rules.ident;
let priv_marker = priv_marker
.unwrap_or_else(|| TokenStream::from(TokenTree::Punct(Punct::new('@', Spacing::Joint))));
let priv_ident = priv_ident.unwrap_or_else(|| format_ident!("__{}", pub_ident));
let mut pub_rules = Punctuated::<MacroRulesRule, Token![;]>::new();
let mut priv_rules = Punctuated::<MacroRulesRule, Token![;]>::new();
for mut rule in mac_rules.rules {
rule.body = replace_macro_invocs(rule.body, pub_ident, &priv_ident, &priv_marker);
if rule.rule.to_string().starts_with(&priv_marker.to_string()) {
priv_rules.push(rule);
} else {
pub_rules.push(rule);
}
}
if pub_rules.is_empty() {
return quote! {
compile_error!("no public rules");
};
}
if priv_rules.is_empty() {
return quote! {
#original
};
}
if original.rules.trailing_punct() {
priv_rules.push_punct(<Token![;]>::default());
pub_rules.push_punct(<Token![;]>::default());
}
mac_rules.rules = pub_rules;
let mut priv_mac_rules = MacroRules {
ident: priv_ident,
rules: priv_rules,
..mac_rules.clone()
};
priv_mac_rules.attrs.retain(|attr| {
if let Some(ident) = attr.path.get_ident() {
ident.to_string() != "doc"
} else {
true
}
});
original.attrs.retain(|attr| {
if let Some(ident) = attr.path.get_ident() {
ident.to_string() != "macro_export" && ident.to_string() != "doc"
} else {
true
}
});
let gen = quote! {
#mac_rules
#[doc(hidden)]
#priv_mac_rules
#[allow(unused_macros)]
#original
};
gen.into()
}
#[cfg(test)]
mod tests;