| use proc_macro2::TokenStream; |
| use quote::quote; |
| use syn::{ |
| parenthesized, |
| parse::{Parse, ParseStream}, |
| Fields, Token, |
| }; |
| |
| use crate::{ |
| diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, |
| utils::{display_pat_members, gen_all_variants_with, gen_unused_pat}, |
| }; |
| use crate::{ |
| fmt::{self, Display}, |
| forward::WhichFn, |
| }; |
| |
| pub enum Url { |
| Display(Display), |
| DocsRs, |
| } |
| |
| impl Parse for Url { |
| fn parse(input: ParseStream) -> syn::Result<Self> { |
| let ident = input.parse::<syn::Ident>()?; |
| if ident == "url" { |
| let la = input.lookahead1(); |
| if la.peek(syn::token::Paren) { |
| let content; |
| parenthesized!(content in input); |
| if content.peek(syn::LitStr) { |
| let fmt = content.parse()?; |
| let args = if content.is_empty() { |
| TokenStream::new() |
| } else { |
| fmt::parse_token_expr(&content, false)? |
| }; |
| let display = Display { |
| fmt, |
| args, |
| has_bonus_display: false, |
| }; |
| Ok(Url::Display(display)) |
| } else { |
| let option = content.parse::<syn::Ident>()?; |
| if option == "docsrs" { |
| Ok(Url::DocsRs) |
| } else { |
| Err(syn::Error::new(option.span(), "Invalid argument to url() sub-attribute. It must be either a string or a plain `docsrs` identifier")) |
| } |
| } |
| } else { |
| input.parse::<Token![=]>()?; |
| Ok(Url::Display(Display { |
| fmt: input.parse()?, |
| args: TokenStream::new(), |
| has_bonus_display: false, |
| })) |
| } |
| } else { |
| Err(syn::Error::new(ident.span(), "not a url")) |
| } |
| } |
| } |
| |
| impl Url { |
| pub(crate) fn gen_enum( |
| enum_name: &syn::Ident, |
| variants: &[DiagnosticDef], |
| ) -> Option<TokenStream> { |
| gen_all_variants_with( |
| variants, |
| WhichFn::Url, |
| |ident, fields, DiagnosticConcreteArgs { url, .. }| { |
| let (pat, fmt, args) = match url.as_ref()? { |
| // fall through to `_ => None` below |
| Url::Display(display) => { |
| let (display_pat, display_members) = display_pat_members(fields); |
| let (fmt, args) = display.expand_shorthand_cloned(&display_members); |
| (display_pat, fmt.value(), args) |
| } |
| Url::DocsRs => { |
| let pat = gen_unused_pat(fields); |
| let fmt = |
| "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}" |
| .into(); |
| let item_path = format!("enum.{}.html#variant.{}", enum_name, ident); |
| let args = quote! { |
| , |
| crate_name=env!("CARGO_PKG_NAME"), |
| crate_version=env!("CARGO_PKG_VERSION"), |
| mod_name=env!("CARGO_PKG_NAME").replace('-', "_"), |
| item_path=#item_path |
| }; |
| (pat, fmt, args) |
| } |
| }; |
| Some(quote! { |
| Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))), |
| }) |
| }, |
| ) |
| } |
| |
| pub(crate) fn gen_struct( |
| &self, |
| struct_name: &syn::Ident, |
| fields: &Fields, |
| ) -> Option<TokenStream> { |
| let (pat, fmt, args) = match self { |
| Url::Display(display) => { |
| let (display_pat, display_members) = display_pat_members(fields); |
| let (fmt, args) = display.expand_shorthand_cloned(&display_members); |
| (display_pat, fmt.value(), args) |
| } |
| Url::DocsRs => { |
| let pat = gen_unused_pat(fields); |
| let fmt = |
| "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}".into(); |
| let item_path = format!("struct.{}.html", struct_name); |
| let args = quote! { |
| , |
| crate_name=env!("CARGO_PKG_NAME"), |
| crate_version=env!("CARGO_PKG_VERSION"), |
| mod_name=env!("CARGO_PKG_NAME").replace('-', "_"), |
| item_path=#item_path |
| }; |
| (pat, fmt, args) |
| } |
| }; |
| Some(quote! { |
| fn url(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> { |
| #[allow(unused_variables, deprecated)] |
| let Self #pat = self; |
| std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))) |
| } |
| }) |
| } |
| } |