#![doc = include_str!("../README.md")]
#![warn(rust_2018_idioms)]
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
self,
parse::{Parse, ParseStream},
parse_macro_input, AttrStyle, Attribute, Error, Lit, LitStr, Meta, MetaNameValue, Result,
};
mod textproc;
#[derive(Clone)]
enum MaybeDocAttr {
Doc(Attribute, MetaNameValue),
Other(Attribute),
}
impl MaybeDocAttr {
fn from_attribute(attr: Attribute) -> Result<Self> {
if attr.path.is_ident("doc") {
let meta = attr.parse_meta()?;
if let Meta::NameValue(nv) = meta {
if let Lit::Str(_) = nv.lit {
Ok(MaybeDocAttr::Doc(attr, nv))
} else {
Err(Error::new(nv.lit.span(), "doc comment must be a string"))
}
} else {
Ok(MaybeDocAttr::Other(attr))
}
} else {
Ok(MaybeDocAttr::Other(attr))
}
}
}
impl ToTokens for MaybeDocAttr {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
MaybeDocAttr::Doc(attr, nv) => {
attr.pound_token.to_tokens(tokens);
if let AttrStyle::Inner(ref b) = attr.style {
b.to_tokens(tokens);
}
attr.bracket_token.surround(tokens, |tokens| {
nv.to_tokens(tokens);
});
}
MaybeDocAttr::Other(attr) => attr.to_tokens(tokens),
}
}
}
impl Into<Attribute> for MaybeDocAttr {
fn into(self) -> Attribute {
match self {
MaybeDocAttr::Doc(mut attr, nv) => {
let lit = nv.lit;
attr.tokens = quote! { = #lit };
attr
}
MaybeDocAttr::Other(attr) => attr,
}
}
}
enum StrOrDocAttrs {
Str(LitStr),
Attrs(Vec<syn::Attribute>),
}
impl Parse for StrOrDocAttrs {
fn parse(input: ParseStream<'_>) -> Result<Self> {
if let Ok(lit_str) = input.parse() {
Ok(Self::Str(lit_str))
} else {
let mut attrs = Attribute::parse_inner(input)?;
attrs.extend(Attribute::parse_outer(input)?);
Ok(Self::Attrs(attrs))
}
}
}
#[proc_macro]
pub fn transform(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: StrOrDocAttrs = parse_macro_input!(tokens);
let (mut iter1, mut iter2);
let iter: &mut dyn Iterator<Item = Result<LitStr>> = match input {
StrOrDocAttrs::Str(s) => {
iter1 = std::iter::once(Ok(s));
&mut iter1
}
StrOrDocAttrs::Attrs(attrs) => {
iter2 = attrs
.into_iter()
.map(|attr| match MaybeDocAttr::from_attribute(attr)? {
MaybeDocAttr::Doc(
_,
syn::MetaNameValue {
lit: syn::Lit::Str(s),
..
},
) => Ok(s),
MaybeDocAttr::Doc(attr, _) | MaybeDocAttr::Other(attr) => {
Err(Error::new_spanned(
&attr,
"only `#[doc = ...]` attributes or a string literal are allowed here",
))
}
});
&mut iter2
}
};
handle_error(|| {
let mut output = String::new();
use textproc::{TextProcOutput, TextProcState};
let mut text_proc = TextProcState::new();
for lit_str in iter {
let lit_str = lit_str?;
let st = lit_str.value();
match text_proc.step(&st, lit_str.span()) {
TextProcOutput::Passthrough => output.push_str(&st),
TextProcOutput::Fragment(fr) => output.push_str(&fr),
TextProcOutput::Empty => {}
}
output.push_str("\n");
}
text_proc.finalize()?;
Ok(LitStr::new(&output, Span::call_site())
.into_token_stream()
.into())
})
}
fn handle_error(cb: impl FnOnce() -> Result<proc_macro::TokenStream>) -> proc_macro::TokenStream {
match cb() {
Ok(tokens) => tokens,
Err(e) => e.to_compile_error().into(),
}
}