1 use proc_macro2::TokenStream; 2 use quote::{quote, ToTokens}; 3 use syn::{Attribute, LitStr, Meta, Result}; 4 5 #[derive(Clone)] 6 pub(crate) struct Display { 7 pub(crate) fmt: LitStr, 8 pub(crate) args: TokenStream, 9 } 10 11 pub(crate) struct VariantDisplay { 12 pub(crate) r#enum: Option<Display>, 13 pub(crate) variant: Display, 14 } 15 16 impl ToTokens for Display { to_tokens(&self, tokens: &mut TokenStream)17 fn to_tokens(&self, tokens: &mut TokenStream) { 18 let fmt = &self.fmt; 19 let args = &self.args; 20 tokens.extend(quote! { 21 write!(formatter, #fmt #args) 22 }); 23 } 24 } 25 26 impl ToTokens for VariantDisplay { to_tokens(&self, tokens: &mut TokenStream)27 fn to_tokens(&self, tokens: &mut TokenStream) { 28 if let Some(ref r#enum) = self.r#enum { 29 r#enum.to_tokens(tokens); 30 tokens.extend(quote! { ?; write!(formatter, ": ")?; }); 31 } 32 self.variant.to_tokens(tokens); 33 } 34 } 35 36 pub(crate) struct AttrsHelper { 37 ignore_extra_doc_attributes: bool, 38 prefix_enum_doc_attributes: bool, 39 } 40 41 impl AttrsHelper { new(attrs: &[Attribute]) -> Self42 pub(crate) fn new(attrs: &[Attribute]) -> Self { 43 let ignore_extra_doc_attributes = attrs 44 .iter() 45 .any(|attr| attr.path().is_ident("ignore_extra_doc_attributes")); 46 let prefix_enum_doc_attributes = attrs 47 .iter() 48 .any(|attr| attr.path().is_ident("prefix_enum_doc_attributes")); 49 50 Self { 51 ignore_extra_doc_attributes, 52 prefix_enum_doc_attributes, 53 } 54 } 55 display(&self, attrs: &[Attribute]) -> Result<Option<Display>>56 pub(crate) fn display(&self, attrs: &[Attribute]) -> Result<Option<Display>> { 57 let displaydoc_attr = attrs.iter().find(|attr| attr.path().is_ident("displaydoc")); 58 59 if let Some(displaydoc_attr) = displaydoc_attr { 60 let lit = displaydoc_attr 61 .parse_args() 62 .expect("#[displaydoc(\"foo\")] must contain string arguments"); 63 let mut display = Display { 64 fmt: lit, 65 args: TokenStream::new(), 66 }; 67 68 display.expand_shorthand(); 69 return Ok(Some(display)); 70 } 71 72 let num_doc_attrs = attrs 73 .iter() 74 .filter(|attr| attr.path().is_ident("doc")) 75 .count(); 76 77 if !self.ignore_extra_doc_attributes && num_doc_attrs > 1 { 78 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."); 79 } 80 81 for attr in attrs { 82 if attr.path().is_ident("doc") { 83 let lit = match &attr.meta { 84 Meta::NameValue(syn::MetaNameValue { 85 value: 86 syn::Expr::Lit(syn::ExprLit { 87 lit: syn::Lit::Str(lit), 88 .. 89 }), 90 .. 91 }) => lit, 92 _ => unimplemented!(), 93 }; 94 95 // Make an attempt at cleaning up multiline doc comments. 96 let doc_str = lit 97 .value() 98 .lines() 99 .map(|line| line.trim().trim_start_matches('*').trim()) 100 .collect::<Vec<&str>>() 101 .join("\n"); 102 103 let lit = LitStr::new(doc_str.trim(), lit.span()); 104 105 let mut display = Display { 106 fmt: lit, 107 args: TokenStream::new(), 108 }; 109 110 display.expand_shorthand(); 111 return Ok(Some(display)); 112 } 113 } 114 115 Ok(None) 116 } 117 display_with_input( &self, r#enum: &[Attribute], variant: &[Attribute], ) -> Result<Option<VariantDisplay>>118 pub(crate) fn display_with_input( 119 &self, 120 r#enum: &[Attribute], 121 variant: &[Attribute], 122 ) -> Result<Option<VariantDisplay>> { 123 let r#enum = if self.prefix_enum_doc_attributes { 124 let result = self 125 .display(r#enum)? 126 .expect("Missing doc comment on enum with #[prefix_enum_doc_attributes]. Please remove the attribute or add a doc comment to the enum itself."); 127 128 Some(result) 129 } else { 130 None 131 }; 132 133 Ok(self 134 .display(variant)? 135 .map(|variant| VariantDisplay { r#enum, variant })) 136 } 137 } 138