use core::panic;
use regex::Regex;
use syn::{FieldsNamed, FieldsUnnamed, Error, LitStr};
use quote::quote;
use proc_macro2::{TokenStream as TokenStream2, Ident, Span};
use crate::{parse::{MessageAttribute, ExitCodeAttribute, FromAttribute, ParsedAttribute, Defaults}, pull_up_results};
pub fn generate_empty_debug_trait(name: &Ident) -> TokenStream2 {
let enum_name = format!("{}", name);
quote!(
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", #enum_name)
}
}
)
}
pub fn generate_empty_error_trait(name: &Ident) -> TokenStream2 {
quote!(
impl std::error::Error for #name {}
)
}
pub fn generate_empty_display_trait(name: &Ident) -> TokenStream2 {
let enum_name = format!("{}", name);
quote!(
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", #enum_name)
}
}
)
}
pub fn generate_empty_termination_trait(name: &Ident) -> TokenStream2 {
quote!(
impl std::process::Termination for #name {
fn report(self) -> std::process::ExitCode {
std::process::ExitCode::SUCCESS
}
}
)
}
pub fn generate_debug_trait(name: &Ident, attributes: &[ParsedAttribute], defaults: &Defaults) -> TokenStream2 {
let debug_impl = attributes.iter().map(|attribute| {
let variant_name = &attribute.variant.ident;
match &attribute.variant.fields {
syn::Fields::Named(f) => message_impl_named(name, variant_name, f, &attribute.message, defaults),
syn::Fields::Unnamed(f) => message_impl_unnamed(name, variant_name, f, &attribute.message, defaults),
syn::Fields::Unit => message_impl_unit(name, variant_name, &attribute.message, defaults),
}
});
quote! {
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#(#debug_impl)*
}
}
}
}
}
pub fn generate_termination_trait(name: &Ident, attributes: &[ParsedAttribute], defaults: &Defaults) -> TokenStream2 {
let termination_impl = attributes.iter().map(|attribute| {
let variant_name = &attribute.variant.ident;
match &attribute.variant.fields {
syn::Fields::Named(f) => termination_impl_named(name, variant_name, f, &attribute.exit_code, defaults),
syn::Fields::Unnamed(f) => termination_impl_unnamed(name, variant_name, f, &attribute.exit_code, defaults),
syn::Fields::Unit => termination_impl_unit(name, variant_name, &attribute.exit_code, defaults),
}
});
quote! {
impl std::process::Termination for #name {
fn report(self) -> std::process::ExitCode {
match self {
#(#termination_impl)*
}
}
}
}
}
pub fn generate_display_trait(name: &Ident, attributes: &[ParsedAttribute], defaults: &Defaults) -> Result<TokenStream2, Error> {
let display_impl = pull_up_results(attributes.iter().map(|attribute| {
let variant_name = &attribute.variant.ident;
if attribute.message.is_none() && defaults.message.is_none() {
return Err(Error::new_spanned(&attribute.variant, "missing #[termination(msg(...))] attribute"));
}
Ok(match &attribute.variant.fields {
syn::Fields::Named(f) => message_impl_named(name, variant_name, f, &attribute.message, defaults),
syn::Fields::Unnamed(f) => message_impl_unnamed(name, variant_name, f, &attribute.message, defaults),
syn::Fields::Unit => message_impl_unit(name, variant_name, &attribute.message, defaults),
})
}))?;
Ok(quote! {
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#(#display_impl)*
}
}
}
})
}
pub fn generate_error_trait(name: &Ident) -> TokenStream2 {
quote! {
impl std::error::Error for #name { }
}
}
pub fn generate_from_traits(name: &Ident, attributes: &[FromAttribute]) -> TokenStream2 {
let from_impl = attributes.iter().map(|attribute| {
let variant_name = &attribute.variant.ident;
if let Some(f_type) = &attribute.from_type {
let fn_impl = match &attribute.variant.fields {
syn::Fields::Named(fields) => {
let field_name = fields.named.first().expect("from with no fields is checked before").ident.as_ref().expect("field without ident?");
quote! { #name::#variant_name { #field_name: value } }
}
syn::Fields::Unnamed(_) => quote! { #name::#variant_name(value) },
syn::Fields::Unit => panic!("from on unit variant should never happen"),
};
quote! {
impl std::convert::From<#f_type> for #name {
fn from(value: #f_type) -> Self {
#fn_impl
}
}
}
} else {
quote!{}
}
});
quote! {
#(#from_impl)*
}
}
fn termination_impl_named(name: &Ident, variant_name: &Ident, fields: &FieldsNamed, exit_code: &Option<ExitCodeAttribute>, defaults: &Defaults) -> TokenStream2 {
let field_names = fields.named.iter().map(|field| &field.ident);
if let Some(ExitCodeAttribute { exit_code, .. }) = exit_code {
quote! { #name::#variant_name { #(ref #field_names),* } => #exit_code.into(), }
} else if let Some(ExitCodeAttribute { exit_code, .. }) = defaults.exit_code {
quote! { #name::#variant_name { #(ref #field_names),* } => #exit_code.into(), }
} else {
quote! { #name::#variant_name { #(ref #field_names),* } => std::process::ExitCode::FAILURE, }
}
}
fn termination_impl_unnamed(name: &Ident, variant_name: &Ident, fields: &FieldsUnnamed, exit_code: &Option<ExitCodeAttribute>, defaults: &Defaults) -> TokenStream2 {
let field_names = fields.unnamed.iter().enumerate().map(|(i, _)| {
syn::Ident::new(&format!("__{}", i), Span::call_site())
});
if let Some(ExitCodeAttribute { exit_code, .. }) = exit_code {
quote! { #name::#variant_name( #(#field_names),* ) => #exit_code.into(), }
} else if let Some(ExitCodeAttribute { exit_code, .. }) = defaults.exit_code {
quote! { #name::#variant_name( #(#field_names),* ) => #exit_code.into(), }
} else {
quote! { #name::#variant_name( #(#field_names),* ) => std::process::ExitCode::FAILURE, }
}
}
fn termination_impl_unit(name: &Ident, variant_name: &Ident, exit_code: &Option<ExitCodeAttribute>, defaults: &Defaults) -> TokenStream2 {
if let Some(ExitCodeAttribute { exit_code, .. }) = exit_code {
quote! { #name::#variant_name => #exit_code.into(), }
} else if let Some(ExitCodeAttribute { exit_code, .. }) = defaults.exit_code {
quote! { #name::#variant_name => #exit_code.into(), }
} else {
quote! { #name::#variant_name => std::process::ExitCode::FAILURE, }
}
}
fn message_impl_named(name: &Ident, variant_name: &Ident, fields: &FieldsNamed, message: &Option<MessageAttribute>, defaults: &Defaults) -> TokenStream2 {
let field_names = fields.named.iter().map(|field| &field.ident);
if let Some(MessageAttribute { format_string_lit, format_string_arguments }) = message {
quote! { #name::#variant_name { #(ref #field_names),* } => write!(f, #format_string_lit, #(#format_string_arguments),*), }
} else if let Some(MessageAttribute { format_string_lit, format_string_arguments }) = &defaults.message {
quote! { #name::#variant_name { #(ref #field_names),* } => write!(f, #format_string_lit, #(#format_string_arguments),*), }
} else {
let self_ident = Ident::new("self", variant_name.span());
quote! { #name::#variant_name { #(ref #field_names),* } => write!(f, "{}", #self_ident), }
}
}
fn get_formatted_string_with_fields(msg: &str, prefix: &str) -> String {
let regex = Regex::new(r#"\{(\d+)(:[^}]+)?\}"#).expect("parsing regex");
regex.replace_all(msg, |caps: ®ex::Captures| {
let field = caps.get(1).expect("the regex always produces one capture group").as_str();
let args = caps.get(2).map_or_else(|| "", |m| m.as_str());
format!("{{{}{}{}}}",prefix, field, args)
}).to_string()
}
fn message_impl_unnamed(name: &Ident, variant_name: &Ident, fields: &FieldsUnnamed, message: &Option<MessageAttribute>, defaults: &Defaults) -> TokenStream2 {
let field_names = fields.unnamed.iter().enumerate().map(|(i, _)| {
syn::Ident::new(&format!("__{}", i), Span::call_site())
});
if let Some(MessageAttribute { format_string_lit, format_string_arguments, .. }) = message {
let format_string = get_formatted_string_with_fields(&format_string_lit.value(), "__");
let updated_lit = LitStr::new(&format_string, format_string_lit.span());
quote! { #name::#variant_name(#(#field_names),*) => write!(f, #updated_lit, #(#format_string_arguments),*), }
} else if let Some(MessageAttribute { format_string_lit, format_string_arguments }) = &defaults.message {
let format_string = get_formatted_string_with_fields(&format_string_lit.value(), "__");
let updated_lit = LitStr::new(&format_string, format_string_lit.span());
quote! { #name::#variant_name(#(#field_names),*) => write!(f, #updated_lit, #(#format_string_arguments),*), }
} else {
let self_ident = Ident::new("self", variant_name.span());
quote! { #name::#variant_name(#(#field_names),*) => write!(f, "{}", #self_ident), }
}
}
fn message_impl_unit(name: &Ident, variant_name: &Ident, message: &Option<MessageAttribute>, defaults: &Defaults) -> TokenStream2 {
if let Some(MessageAttribute { format_string_lit, format_string_arguments, .. }) = message {
quote! { #name::#variant_name => write!(f, #format_string_lit, #(#format_string_arguments),*), }
} else if let Some(MessageAttribute { format_string_lit, format_string_arguments }) = &defaults.message {
quote! { #name::#variant_name => write!(f, #format_string_lit, #(#format_string_arguments),*), }
} else {
let self_ident = Ident::new("self", variant_name.span());
quote! { #name::#variant_name => write!(f, "{}", #self_ident), }
}
}