use proc_macro2::{Literal, Span, TokenTree};
use syn::{
Ident, LitBool, Token, Type, Visibility, bracketed, parse::Parse, punctuated::Punctuated,
};
macro_rules! syn_error {
($message: literal) => {
syn::Result::Err(syn::Error::new(Span::call_site(), $message))
};
}
#[derive(Debug, Default)]
pub(crate) struct Settings {
pub generate_structs: bool,
pub delete_template: bool,
pub delete_empty_tuple_fields: bool,
}
pub(crate) struct AttrInputs {
pub settings: Settings,
pub derived_structs: Vec<Derived>,
}
impl Parse for AttrInputs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut settings = Settings::default();
let mut settings_fork_cursor = input.fork().cursor();
let mut has_settings = false;
let mut token_number_first_half = 0;
while let Some((tt, next)) = settings_fork_cursor.token_tree() {
token_number_first_half += 1;
match &tt {
TokenTree::Punct(punct) if punct.as_char() == ';' => {
has_settings = true;
break;
}
_ => settings_fork_cursor = next,
}
}
match has_settings {
false => settings = Settings::default(),
true => {
if token_number_first_half % 4 != 0 {
return syn_error!(
"Something went wrong with parsing the derived struct settings"
);
}
for _ in 0..(token_number_first_half >> 2) {
let ident = input.parse::<Ident>()?;
let _ = input.parse::<Token![:]>()?;
if input.peek(LitBool) {
let value = input.parse::<LitBool>()?;
match ident.to_string().as_str() {
"GenStructs" => settings.generate_structs = value.value,
"DeleteTemplate" => settings.delete_template = value.value,
"OmitEmptyTupleFields" => {
settings.delete_empty_tuple_fields = value.value
}
_ => (),
}
} else {
return syn_error!("Invalid setting value");
}
let lookahead = input.lookahead1();
if lookahead.peek(Token![;]) {
let _ = input.parse::<Token![;]>();
} else if lookahead.peek(Token![,]) {
let _ = input.parse::<Token![,]>();
} else {
return syn_error!("Invalid character");
}
}
}
}
let parsed: Punctuated<Derived, Token![,]> = Punctuated::parse_terminated(input)?;
Ok(Self {
settings,
derived_structs: parsed.into_iter().collect(),
})
}
}
pub(crate) struct Derived {
pub vis: Visibility,
pub name: Ident,
pub fields: std::collections::HashMap<String, Type>,
}
impl Parse for Derived {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let vis = input.parse::<Visibility>()?;
let name = input.parse::<Ident>()?;
let _ = input.parse::<Token![:]>()?;
let field_list;
bracketed!(field_list in input);
let mut fields = std::collections::HashMap::new();
for (ident_number, field) in
(<Punctuated<FieldDescriptor, Token![,]>>::parse_separated_nonempty(&field_list)?)
.into_iter()
.enumerate()
{
let ident = match field.ident {
Some(ident) => ident,
None => ident_number.to_string(),
};
fields.insert(ident, field.field_type);
}
Ok(Self { name, fields, vis })
}
}
pub(crate) struct FieldDescriptor {
ident: Option<String>,
field_type: Type,
}
impl Parse for FieldDescriptor {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident = match (input.peek2(Token![:]), input.peek(Ident)) {
(true, true) => {
let out = input.parse::<Ident>()?;
let _ = input.parse::<Token![:]>()?;
Some(out.to_string())
}
(true, false) => {
let x = input.parse::<Literal>()?;
let out = x.to_string();
if out.chars().any(|c| !c.is_numeric()) {
return syn_error!("Literal is not number");
}
let _ = input.parse::<Token![:]>()?;
Some(out)
}
(false, true) => return syn_error!("Found nonesense colon"),
(false, _) => None,
};
let field_type = input.parse::<Type>()?;
Ok(Self { ident, field_type })
}
}
pub(crate) struct EitherMacro(pub Vec<Type>);
impl Parse for EitherMacro {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let parsed: Punctuated<Type, Token![|]> = Punctuated::parse_separated_nonempty(input)?;
Ok(Self(parsed.into_iter().collect()))
}
}