derive_constructors_proc 1.0.0

Deriving From, TryFrom and create new_with_*args* functions.
Documentation
use syn::{Attribute, DataStruct, DeriveInput, parse_str, Type};
use quote::{quote, ToTokens};
use proc_macro2::Ident;
use proc_macro::TokenStream;
use std::collections::HashMap;
use convert_case::{Case, Casing};
use crate::utils::idents_and_groups_from;
use crate::utils::{ExpectElseOption, ExpectElseResult, extract_token_stream_of_attribute, find_attribute, print_info};

pub(crate) struct FieldsInfo {
    pub(crate) fields_names: Vec<Ident>,
    pub(crate) fields_types: Vec<Type>,
    pub(crate) no_from_fields: Vec<Ident>,
    pub(crate) no_from_fields_initializers: Vec<proc_macro2::TokenStream>,
}

impl FieldsInfo {
    pub(crate) fn new_from_derive_data_struct(data: &DataStruct) -> FieldsInfo {
        let (fields_names, fields_types) = data.fields.iter()
            .filter(|field| find_attribute(&field.attrs, "no_from").is_none())
            .map(|field| (field.ident.clone().unwrap(), field.ty.clone()))
            .unzip::<_, _, Vec<_>, Vec<_>>();
        print_info(|| "Fields", || format!("{fields_names:#?}"));

        let (no_from_fields, no_from_fields_initializers) = data.fields.iter()
            .map(|field| (field, find_attribute(&field.attrs, "no_from")))
            .filter(|(_, attribute_opt)| attribute_opt.is_some())
            .map(|(field, attribute_opt)| (field, attribute_opt.unwrap()))
            .map(|(field, attribute)| {
                let initializer = extract_token_stream_of_attribute(attribute).map(Into::into).unwrap_or_else(|| quote!(core::default::Default::default()));
                print_info(|| "Ident", || format!("{:#?}", field.ident.as_ref()));
                (field.ident.clone().unwrap(), initializer)
            })
            .unzip::<_, _, Vec<_>, Vec<_>>();
        print_info(|| "No from fields", || format!("{no_from_fields:#?}"));

        let info = FieldsInfo { fields_names, fields_types, no_from_fields, no_from_fields_initializers };
        info
    }

    pub(crate) fn new_from_macro_attribute_info(data: &DataStruct, attr_contents: &mut HashMap<String, proc_macro2::TokenStream>) -> FieldsInfo {
        let mut idents_and_groups = attr_contents;
        print_info(|| "Info", || format!("{idents_and_groups:#?}"));

        let (mut no_from_fields, mut no_from_initializers) = idents_and_groups.remove("defaults")
            .map(|token| idents_and_groups_from(token.to_token_stream())

                .expect_else(|_| "Could not resolve groups and descriptions inside attribute 'defaults'")
                .into_iter()
                .map(|(ident, group)| (ident, group))
                .unzip::<_, _, Vec<_>, Vec<_>>())
            .unwrap_or_else(|| Default::default());

        let fields_in_use = idents_and_groups.remove("fields")
            .map(|fields_token| fields_token.to_string()

                .split(",")
                .map(|field_name| parse_str::<Ident>(field_name.trim()).unwrap())
                .collect::<Vec<_>>()
            )
            .unwrap_or_else(|| data.fields.iter()

                .filter(|field| !no_from_fields.contains(field.ident.as_ref().unwrap()))
                .map(|field| field.ident.clone().unwrap()).collect());

        let fields_in_use_types = fields_in_use.iter()
            .map(|constructor_field| {
                data.fields.iter().filter(|field| field.ident.as_ref().is_some_and(|ident| ident.eq(constructor_field))).next().unwrap()
                    .ty.clone()
            })
            .collect::<Vec<_>>();

        let (unreached_field, unreached_initializers) =
            data.fields.iter()
                .map(|field| field.ident.clone().unwrap())
                .filter(|name| !fields_in_use.contains(name) && !no_from_fields.contains(name))
                .map(|name| (name, quote! {core::default::Default::default()}))
                .unzip::<_, _, Vec<_>, Vec<_>>();

        no_from_fields.extend(unreached_field.into_iter());
        no_from_initializers.extend(unreached_initializers.into_iter());

        FieldsInfo {
            fields_names: fields_in_use,
            fields_types: fields_in_use_types,
            no_from_fields: no_from_fields,
            no_from_fields_initializers: no_from_initializers,
        }
    }
}

pub(crate) struct TryFromInfo {
    pub(crate) error_enum_metadata: proc_macro2::TokenStream,
    pub(crate) error_enum_name: Ident,
    pub(crate) error_types: Vec<Ident>,
    pub(crate) try_from_types: Vec<Ident>,
}

impl TryFromInfo {
    fn error_types_and_try_from_types(fields_names: &Vec<Ident>) -> (Vec<Ident>, Vec<Ident>) {
        let error_types = fields_names.iter()
            .map(|field_name|
                syn::parse_str::<Ident>(
                    &format!("{}Error", field_name.to_string().to_case(Case::Pascal))
                ).expect_else(|_| format!("Could not create enum error's identifier name for field {field_name}")))
            .collect::<Vec<_>>();

        let try_from_types = fields_names.iter()
            .map(|field_name|
                syn::parse_str::<Ident>(
                    &format!("{}From", field_name.to_string().to_case(Case::Pascal))
                ).expect_else(|_| format!("Could not create enum error's identifier name for field {field_name}")))
            .collect::<Vec<_>>();
        (error_types, try_from_types)
    }

    pub(crate) fn new_from_derive_data_struct(name: &Ident, attrs: &Vec<Attribute>, fields_names: &Vec<Ident>) -> TryFromInfo {
        let error_enum_metadata: proc_macro2::TokenStream = find_attribute(&attrs, "enum_error_meta")
            .map(|attribute| extract_token_stream_of_attribute(attribute)

                .expect_else(|| "Could not parse content of the #[enum_error_meta] attribute"))
            .unwrap_or_else(|| TokenStream::new()).into();

        let error_enum_name = syn::parse_str::<Ident>
            (&format!("{}TryFromError", name.to_string().to_case(Case::Pascal)))
            .expect_else(|_| format!("Could not create enum error's identifier name"));

        let (error_types, try_from_types) = Self::error_types_and_try_from_types(fields_names);

        Self {
            error_enum_metadata,
            error_enum_name,
            error_types,
            try_from_types,
        }
    }


    pub(crate) fn new_from_macro_attribute_info(derive_input: &DeriveInput, fields_info: &FieldsInfo, constructor_fn_name: Option<&Ident>, attr_contents: &mut HashMap<String, proc_macro2::TokenStream>) -> Self {
        let error_enum_metadata = attr_contents.remove("error_enum_metadata")
            .unwrap_or_else(proc_macro2::TokenStream::new);
        let error_enum_name = attr_contents.remove("error_enum_named")
            .map(|name| syn::parse::<Ident>(name.into()).unwrap())
            .unwrap_or_else(|| {
                let constructor_fn_name = constructor_fn_name.map(|constructor_name| constructor_name.to_string()).unwrap_or_else(|| "TryFrom".to_string());
                syn::parse_str::<Ident>
                    (&format!("{}_{}_error", derive_input.ident, constructor_fn_name).to_case(Case::Pascal))
                    .expect_else(|_| format!("Could not create enum error's identifier name"))
            });
        let (error_types, try_from_types) = Self::error_types_and_try_from_types(&fields_info.fields_names);

        Self {
            error_enum_metadata,
            error_enum_name,
            error_types,
            try_from_types,
        }
    }
}