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()? {
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)))
}
})
}
}