use proc_macro2::TokenStream; use quote::quote; use syn::{ parenthesized, parse::{Parse, ParseStream}, Token, }; use crate::diagnostic::DiagnosticVariant; pub struct Help { pub fmt: String, pub args: Vec, } impl Parse for Help { fn parse(input: ParseStream) -> syn::Result { let ident = input.parse::()?; if ident == "help" { let la = input.lookahead1(); if la.peek(syn::token::Paren) { let content; parenthesized!(content in input); let mut fmt = None; let mut args = Vec::new(); let punc = syn::punctuated::Punctuated::::parse_terminated( &content, )?; for (i, arg) in punc.into_iter().enumerate() { if i == 0 { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(str), .. }) = arg { fmt = Some(str.value()); } } else { args.push(arg); } } if let Some(fmt) = fmt { Ok(Help { fmt, args }) } else if !args.is_empty() { Err(syn::Error::new( ident.span(), "The first arg to help() must be a literal format string.", )) } else { Err(syn::Error::new( ident.span(), "help() format string is required", )) } } else { input.parse::()?; Ok(Help { fmt: input.parse::()?.value(), args: Vec::new(), }) } } else { Err(syn::Error::new(ident.span(), "not a help")) } } } impl Help { pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option { let help_pairs = variants .iter() .filter(|v| v.help.is_some()) .map( |DiagnosticVariant { ref ident, ref help, ref fields, .. }| { let help = &help.as_ref().unwrap(); let fmt = &help.fmt; let args = &help.args; match fields { syn::Fields::Named(_) => { quote! { Self::#ident{..} => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), } } syn::Fields::Unnamed(_) => { quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), } } syn::Fields::Unit => quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))), }, } }, ) .collect::>(); if help_pairs.is_empty() { None } else { Some(quote! { fn help<'a>(&'a self) -> std::option::Option> { match self { #(#help_pairs)* _ => None, } } }) } } pub(crate) fn gen_struct(&self) -> Option { let fmt = &self.fmt; let args = &self.args; Some(quote! { fn help<'a>(&'a self) -> std::option::Option> { std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #(#args),*))) } }) } }