extern crate proc_macro;
extern crate proc_macro2;
use proc_macro2::{Punct, Spacing, Span, TokenStream, TokenTree};
use quote::{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 run_always = false;
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),
arg.lit,
) {
(Some("always"), Lit::Bool(val)) => run_always = val.value,
(Some("impl"), Lit::Str(val)) => {
priv_marker = Some(TokenStream::from_str(&val.value()).expect("Invalid tokens"))
}
(Some("internal"), Lit::Str(val)) => {
priv_ident = Some(val.parse().expect("Expected valid identifier"))
}
_ => {
let path = &arg.path;
let path = quote!(#path).to_string();
return quote_spanned! {
arg.path.span()=> compile_error!(concat!("invalid argument: ", #path));
}
},
};
}
}
if !run_always && std::env::var("doc").is_err() {
return quote! {
#mac_rules
};
}
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(|| Ident::new(&format!("__{}", pub_ident.to_string()), Span::call_site()));
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 priv_rules.is_empty() {
return quote! {
#original
};
}
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;