extern crate proc_macro;
use quote::{quote, ToTokens};
use syn::{
bracketed,
parse::{Parse, ParseStream},
parse_macro_input,
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
Error,
Expr,
Ident,
ImplItemFn,
ItemImpl,
Lit,
Meta,
PathArguments,
Token,
};
use crate::{macro_error, parse_name};
pub(crate) fn deeplink(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut impl_fn = parse_macro_input!(item as ImplItemFn);
for attr in &mut impl_fn.attrs {
if attr.path() != &parse_quote! { doc } {
continue;
}
let mut text = match &mut attr.meta {
Meta::NameValue(nv) => match &mut nv.value {
Expr::Lit(el) => match &mut el.lit {
Lit::Str(ls) => ls.value(),
_ => continue,
},
_ => continue,
},
_ => continue,
};
while let Some(ix) = text.find("d[") {
let pre = &text[..ix];
let rest = &text[ix + 2..];
let end = match rest.find(']') {
Some(v) => v,
None => macro_error!(attr.span(), "unterminated d["),
};
let body = &rest[..end];
let post = &rest[end + 1..];
let (fixed, body) = if body.starts_with('`') && body.ends_with('`') {
(
true,
body.strip_prefix('`').unwrap().strip_suffix('`').unwrap(),
)
} else {
(false, body)
};
let mut new_text = pre.to_owned();
if fixed {
new_text.push_str("<code>");
}
new_text.push_str(&text_link(body));
if fixed {
new_text.push_str("</code>");
}
new_text.push_str(post);
text = new_text;
}
*attr = parse_quote! { #[doc = #text] };
}
impl_fn.into_token_stream().into()
}
fn text_link(text: &str) -> String {
let segments = text.split_inclusive(&['<', '>'])
.flat_map(|s| {
if s == "<" || s == ">" {
vec![s]
} else if let Some(sub) = s.strip_suffix(['<', '>']) {
vec![sub, &s[sub.len()..]]
} else {
vec![s]
}
});
let mut out = vec![];
for segment in segments {
match segment {
"<" => out.push("<"),
">" => out.push(">"),
"()" => out.push("()"),
_ => {
let short = segment
.rsplit_once("::")
.map(|(_, short)| short)
.unwrap_or(segment);
out.extend(["[", short, "](", segment, ")"]);
}
}
}
out.concat()
}
pub(crate) fn options_doc(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
custom_tokens: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let setters = parse_macro_input!(attr as ItemImpl);
let mut impl_fn = parse_macro_input!(item as ImplItemFn);
let args = parse_macro_input!(custom_tokens as OptionsDocArgs);
let mut setter_names = vec![];
for item in &setters.items {
match item {
syn::ImplItem::Fn(item) if matches!(item.vis, syn::Visibility::Public(..)) => {
setter_names.push(item.sig.ident.to_token_stream().to_string());
}
_ => continue,
}
}
let mut doc_path = match &*setters.self_ty {
syn::Type::Path(p) => p.path.clone(),
t => macro_error!(t.span(), "invalid options doc argument"),
};
for seg in &mut doc_path.segments {
seg.arguments = PathArguments::None;
}
let doc_path = doc_path.to_token_stream().to_string();
impl_fn.attrs.push(parse_quote! {
#[doc = ""]
});
let preamble = format!(
"These methods can be chained before `{}` to set options:",
args.term
.as_ref()
.map(|(_, b)| b.as_str())
.unwrap_or(".await")
);
impl_fn.attrs.push(parse_quote! {
#[doc = #preamble]
});
for name in setter_names {
let docstr = format!(" * [`{0}`]({1}::{0})", name, doc_path);
impl_fn.attrs.push(parse_quote! {
#[doc = #docstr]
});
}
impl_fn.into_token_stream().into()
}
pub(crate) struct OptionsDocArgs {
foreign_path: syn::Path,
term: Option<(Token![,], String)>,
}
impl Parse for OptionsDocArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let foreign_path = input.parse()?;
let term = if input.is_empty() {
None
} else {
let (comma, lit) = (input.parse()?, input.parse::<syn::LitStr>()?);
Some((comma, lit.value()))
};
Ok(Self { foreign_path, term })
}
}
impl ToTokens for OptionsDocArgs {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(self.foreign_path.to_token_stream());
if let Some((comma, lit)) = &self.term {
tokens.extend(comma.to_token_stream());
tokens.extend(lit.to_token_stream());
}
}
}
impl macro_magic::mm_core::ForeignPath for OptionsDocArgs {
fn foreign_path(&self) -> &syn::Path {
&self.foreign_path
}
}
pub(crate) fn export_doc(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = parse_macro_input!(attr as ExportDocArgs);
let impl_in = parse_macro_input!(item as ItemImpl);
let mut doc_impl = impl_in.clone();
if let Some(extra) = args.extra {
for name in &extra {
doc_impl.items.push(parse_quote! {
pub fn #name(&self) {}
});
}
}
let doc_name = args.name;
quote! {
#impl_in
#[macro_magic::export_tokens_no_emit(#doc_name)]
#doc_impl
}
.into()
}
struct ExportDocArgs {
name: Ident,
extra: Option<Vec<Ident>>, }
impl Parse for ExportDocArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
let mut out = Self { name, extra: None };
if input.parse::<Option<Token![,]>>()?.is_none() || input.is_empty() {
return Ok(out);
}
parse_name(input, "extra")?;
input.parse::<Token![=]>()?;
let content;
bracketed!(content in input);
let punc = Punctuated::<Ident, Token![,]>::parse_terminated(&content)?;
out.extra = Some(punc.into_pairs().map(|p| p.into_value()).collect());
Ok(out)
}
}