use proc_macro2::TokenStream; | |
use quote::{quote, ToTokens}; | |
use syn::{Attribute, LitStr, Meta, Result}; | |
#[derive(Clone)] | |
pub(crate) struct Display { | |
pub(crate) fmt: LitStr, | |
pub(crate) args: TokenStream, | |
} | |
pub(crate) struct VariantDisplay { | |
pub(crate) r#enum: Option<Display>, | |
pub(crate) variant: Display, | |
} | |
impl ToTokens for Display { | |
fn to_tokens(&self, tokens: &mut TokenStream) { | |
let fmt = &self.fmt; | |
let args = &self.args; | |
tokens.extend(quote! { | |
write!(formatter, #fmt #args) | |
}); | |
} | |
} | |
impl ToTokens for VariantDisplay { | |
fn to_tokens(&self, tokens: &mut TokenStream) { | |
if let Some(ref r#enum) = self.r#enum { | |
r#enum.to_tokens(tokens); | |
tokens.extend(quote! { ?; write!(formatter, ": ")?; }); | |
} | |
self.variant.to_tokens(tokens); | |
} | |
} | |
pub(crate) struct AttrsHelper { | |
ignore_extra_doc_attributes: bool, | |
prefix_enum_doc_attributes: bool, | |
} | |
impl AttrsHelper { | |
pub(crate) fn new(attrs: &[Attribute]) -> Self { | |
let ignore_extra_doc_attributes = attrs | |
.iter() | |
.any(|attr| attr.path().is_ident("ignore_extra_doc_attributes")); | |
let prefix_enum_doc_attributes = attrs | |
.iter() | |
.any(|attr| attr.path().is_ident("prefix_enum_doc_attributes")); | |
Self { | |
ignore_extra_doc_attributes, | |
prefix_enum_doc_attributes, | |
} | |
} | |
pub(crate) fn display(&self, attrs: &[Attribute]) -> Result<Option<Display>> { | |
let displaydoc_attr = attrs.iter().find(|attr| attr.path().is_ident("displaydoc")); | |
if let Some(displaydoc_attr) = displaydoc_attr { | |
let lit = displaydoc_attr | |
.parse_args() | |
.expect("#[displaydoc(\"foo\")] must contain string arguments"); | |
let mut display = Display { | |
fmt: lit, | |
args: TokenStream::new(), | |
}; | |
display.expand_shorthand(); | |
return Ok(Some(display)); | |
} | |
let num_doc_attrs = attrs | |
.iter() | |
.filter(|attr| attr.path().is_ident("doc")) | |
.count(); | |
if !self.ignore_extra_doc_attributes && num_doc_attrs > 1 { | |
panic!("Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive."); | |
} | |
for attr in attrs { | |
if attr.path().is_ident("doc") { | |
let lit = match &attr.meta { | |
Meta::NameValue(syn::MetaNameValue { | |
value: | |
syn::Expr::Lit(syn::ExprLit { | |
lit: syn::Lit::Str(lit), | |
.. | |
}), | |
.. | |
}) => lit, | |
_ => unimplemented!(), | |
}; | |
// Make an attempt at cleaning up multiline doc comments. | |
let doc_str = lit | |
.value() | |
.lines() | |
.map(|line| line.trim().trim_start_matches('*').trim()) | |
.collect::<Vec<&str>>() | |
.join("\n"); | |
let lit = LitStr::new(doc_str.trim(), lit.span()); | |
let mut display = Display { | |
fmt: lit, | |
args: TokenStream::new(), | |
}; | |
display.expand_shorthand(); | |
return Ok(Some(display)); | |
} | |
} | |
Ok(None) | |
} | |
pub(crate) fn display_with_input( | |
&self, | |
r#enum: &[Attribute], | |
variant: &[Attribute], | |
) -> Result<Option<VariantDisplay>> { | |
let r#enum = if self.prefix_enum_doc_attributes { | |
let result = self | |
.display(r#enum)? | |
.expect("Missing doc comment on enum with #[prefix_enum_doc_attributes]. Please remove the attribute or add a doc comment to the enum itself."); | |
Some(result) | |
} else { | |
None | |
}; | |
Ok(self | |
.display(variant)? | |
.map(|variant| VariantDisplay { r#enum, variant })) | |
} | |
} |