1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{
4 parenthesized,
5 parse::{Parse, ParseStream},
6 Fields, Token,
7};
8
9use crate::{
10 diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
11 utils::{display_pat_members, gen_all_variants_with, gen_unused_pat},
12};
13use crate::{
14 fmt::{self, Display},
15 forward::WhichFn,
16};
17
18pub enum Url {
19 Display(Display),
20 DocsRs,
21}
22
23impl Parse for Url {
24 fn parse(input: ParseStream) -> syn::Result<Self> {
25 let ident = input.parse::<syn::Ident>()?;
26 if ident == "url" {
27 let la = input.lookahead1();
28 if la.peek(syn::token::Paren) {
29 let content;
30 parenthesized!(content in input);
31 if content.peek(syn::LitStr) {
32 let fmt = content.parse()?;
33 let args = if content.is_empty() {
34 TokenStream::new()
35 } else {
36 fmt::parse_token_expr(&content, false)?
37 };
38 let display = Display {
39 fmt,
40 args,
41 has_bonus_display: false,
42 };
43 Ok(Url::Display(display))
44 } else {
45 let option = content.parse::<syn::Ident>()?;
46 if option == "docsrs" {
47 Ok(Url::DocsRs)
48 } else {
49 Err(syn::Error::new(option.span(), "Invalid argument to url() sub-attribute. It must be either a string or a plain `docsrs` identifier"))
50 }
51 }
52 } else {
53 input.parse::<Token![=]>()?;
54 Ok(Url::Display(Display {
55 fmt: input.parse()?,
56 args: TokenStream::new(),
57 has_bonus_display: false,
58 }))
59 }
60 } else {
61 Err(syn::Error::new(ident.span(), "not a url"))
62 }
63 }
64}
65
66impl Url {
67 pub(crate) fn gen_enum(
68 enum_name: &syn::Ident,
69 variants: &[DiagnosticDef],
70 ) -> Option<TokenStream> {
71 gen_all_variants_with(
72 variants,
73 WhichFn::Url,
74 |ident, fields, DiagnosticConcreteArgs { url, .. }| {
75 let (pat, fmt, args) = match url.as_ref()? {
76 Url::Display(display) => {
78 let (display_pat, display_members) = display_pat_members(fields);
79 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
80 (display_pat, fmt.value(), args)
81 }
82 Url::DocsRs => {
83 let pat = gen_unused_pat(fields);
84 let fmt =
85 "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}"
86 .into();
87 let item_path = format!("enum.{}.html#variant.{}", enum_name, ident);
88 let args = quote! {
89 ,
90 crate_name=env!("CARGO_PKG_NAME"),
91 crate_version=env!("CARGO_PKG_VERSION"),
92 mod_name=env!("CARGO_PKG_NAME").replace('-', "_"),
93 item_path=#item_path
94 };
95 (pat, fmt, args)
96 }
97 };
98 Some(quote! {
99 Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
100 })
101 },
102 )
103 }
104
105 pub(crate) fn gen_struct(
106 &self,
107 struct_name: &syn::Ident,
108 fields: &Fields,
109 ) -> Option<TokenStream> {
110 let (pat, fmt, args) = match self {
111 Url::Display(display) => {
112 let (display_pat, display_members) = display_pat_members(fields);
113 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
114 (display_pat, fmt.value(), args)
115 }
116 Url::DocsRs => {
117 let pat = gen_unused_pat(fields);
118 let fmt =
119 "https://docs.rs/{crate_name}/{crate_version}/{mod_name}/{item_path}".into();
120 let item_path = format!("struct.{}.html", struct_name);
121 let args = quote! {
122 ,
123 crate_name=env!("CARGO_PKG_NAME"),
124 crate_version=env!("CARGO_PKG_VERSION"),
125 mod_name=env!("CARGO_PKG_NAME").replace('-', "_"),
126 item_path=#item_path
127 };
128 (pat, fmt, args)
129 }
130 };
131 Some(quote! {
132 fn url(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
133 #[allow(unused_variables, deprecated)]
134 let Self #pat = self;
135 std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
136 }
137 })
138 }
139}