summaryrefslogtreecommitdiff
path: root/src/url.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/url.rs')
-rw-r--r--src/url.rs139
1 files changed, 139 insertions, 0 deletions
diff --git a/src/url.rs b/src/url.rs
new file mode 100644
index 0000000..734d1a4
--- /dev/null
+++ b/src/url.rs
@@ -0,0 +1,139 @@
+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)))
+ }
+ })
+ }
+}