1#![doc = include_str!("../README.md")]
2#![warn(rust_2018_idioms)]
3use proc_macro2::{Span, TokenStream};
4use quote::{quote, ToTokens};
5use syn::{
6 self,
7 parse::{Parse, ParseStream},
8 parse_macro_input, AttrStyle, Attribute, Error, Lit, LitStr, Meta, MetaNameValue, Result,
9};
10
11mod textproc;
12
13#[derive(Clone)]
15enum MaybeDocAttr {
16 Doc(Attribute, MetaNameValue),
22 Other(Attribute),
24}
25
26impl MaybeDocAttr {
27 fn from_attribute(attr: Attribute) -> Result<Self> {
28 if attr.path.is_ident("doc") {
29 let meta = attr.parse_meta()?;
30
31 if let Meta::NameValue(nv) = meta {
32 if let Lit::Str(_) = nv.lit {
33 Ok(MaybeDocAttr::Doc(attr, nv))
34 } else {
35 Err(Error::new(nv.lit.span(), "doc comment must be a string"))
36 }
37 } else {
38 Ok(MaybeDocAttr::Other(attr))
40 }
41 } else {
42 Ok(MaybeDocAttr::Other(attr))
43 }
44 }
45}
46
47impl ToTokens for MaybeDocAttr {
48 fn to_tokens(&self, tokens: &mut TokenStream) {
49 match self {
50 MaybeDocAttr::Doc(attr, nv) => {
51 attr.pound_token.to_tokens(tokens);
52 if let AttrStyle::Inner(ref b) = attr.style {
53 b.to_tokens(tokens);
54 }
55 attr.bracket_token.surround(tokens, |tokens| {
56 nv.to_tokens(tokens);
57 });
58 }
59 MaybeDocAttr::Other(attr) => attr.to_tokens(tokens),
60 }
61 }
62}
63
64impl Into<Attribute> for MaybeDocAttr {
65 fn into(self) -> Attribute {
67 match self {
68 MaybeDocAttr::Doc(mut attr, nv) => {
69 let lit = nv.lit;
70 attr.tokens = quote! { = #lit };
71 attr
72 }
73 MaybeDocAttr::Other(attr) => attr,
74 }
75 }
76}
77
78enum StrOrDocAttrs {
79 Str(LitStr),
80 Attrs(Vec<syn::Attribute>),
81}
82
83impl Parse for StrOrDocAttrs {
84 fn parse(input: ParseStream<'_>) -> Result<Self> {
85 if let Ok(lit_str) = input.parse() {
86 Ok(Self::Str(lit_str))
87 } else {
88 let mut attrs = Attribute::parse_inner(input)?;
90 attrs.extend(Attribute::parse_outer(input)?);
91 Ok(Self::Attrs(attrs))
92 }
93 }
94}
95
96#[proc_macro]
101pub fn transform(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
102 let input: StrOrDocAttrs = parse_macro_input!(tokens);
103 let (mut iter1, mut iter2);
104 let iter: &mut dyn Iterator<Item = Result<LitStr>> = match input {
105 StrOrDocAttrs::Str(s) => {
106 iter1 = std::iter::once(Ok(s));
107 &mut iter1
108 }
109 StrOrDocAttrs::Attrs(attrs) => {
110 iter2 = attrs
111 .into_iter()
112 .map(|attr| match MaybeDocAttr::from_attribute(attr)? {
113 MaybeDocAttr::Doc(
114 _,
115 syn::MetaNameValue {
116 lit: syn::Lit::Str(s),
117 ..
118 },
119 ) => Ok(s),
120 MaybeDocAttr::Doc(attr, _) | MaybeDocAttr::Other(attr) => {
121 Err(Error::new_spanned(
122 &attr,
123 "only `#[doc = ...]` attributes or a string literal are allowed here",
124 ))
125 }
126 });
127 &mut iter2
128 }
129 };
130
131 handle_error(|| {
132 let mut output = String::new();
133 use textproc::{TextProcOutput, TextProcState};
134 let mut text_proc = TextProcState::new();
135 for lit_str in iter {
136 let lit_str = lit_str?;
137 let st = lit_str.value();
138 match text_proc.step(&st, lit_str.span()) {
139 TextProcOutput::Passthrough => output.push_str(&st),
140 TextProcOutput::Fragment(fr) => output.push_str(&fr),
141 TextProcOutput::Empty => {}
142 }
143 output.push_str("\n");
144 }
145 text_proc.finalize()?;
146
147 Ok(LitStr::new(&output, Span::call_site())
148 .into_token_stream()
149 .into())
150 })
151}
152
153fn handle_error(cb: impl FnOnce() -> Result<proc_macro::TokenStream>) -> proc_macro::TokenStream {
154 match cb() {
155 Ok(tokens) => tokens,
156 Err(e) => e.to_compile_error().into(),
157 }
158}