bool_to_bitflags 0.1.3

A library to compact multiple bools into a single bitflags field automatically with getters and setters.
Documentation
use std::borrow::Cow;

use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};

use crate::{
    args::Args,
    r#impl::{extract_cfgs, generate_pub_crate, ty_from_ident, BoolField},
};

fn extract_docs(attrs: &[syn::Attribute]) -> TokenStream {
    let attrs = attrs.iter().filter(|attr| attr.path().is_ident("doc"));
    quote!(#(#attrs)*)
}

fn handle_visibility_arg(field_vis: &syn::Visibility, private: bool) -> Cow<'_, syn::Visibility> {
    if private {
        Cow::Owned(generate_pub_crate())
    } else {
        Cow::Borrowed(field_vis)
    }
}

fn handle_owning_setters(owning_setters: bool) -> (TokenStream, syn::Type, Option<Ident>) {
    let self_ident = Ident::new("self", Span::call_site());
    if owning_setters {
        let owned_self_ty = ty_from_ident(Ident::new("Self", Span::call_site()));
        (quote!(mut self), owned_self_ty, Some(self_ident))
    } else {
        let unit_ret = syn::Type::Tuple(syn::TypeTuple {
            paren_token: syn::token::Paren::default(),
            elems: syn::punctuated::Punctuated::new(),
        });

        (quote!(&mut self), unit_ret, None)
    }
}

fn args_to_names(args: &Args, field_name: &Ident) -> (Ident, Ident) {
    let getter_prefix = args.getter_prefix.as_deref().unwrap_or("");
    let setter_prefix = args.setter_prefix.as_deref().unwrap_or("set_");
    let span = field_name.span();

    (
        format_ident!("{getter_prefix}{field_name}", span = span),
        format_ident!("{setter_prefix}{field_name}", span = span),
    )
}

pub fn generate_getter_body(
    field: &BoolField,
    flag_field: &Ident,
    flags_name: &Ident,
) -> TokenStream {
    let flag_name = &field.flag_ident;
    match field {
        BoolField::Normal(..) => quote!(self.#flag_field.contains(#flags_name::#flag_name)),
        BoolField::Opt {
            tag_bit_flag_ident, ..
        } => quote!(
            if self.#flag_field.contains(#flags_name::#tag_bit_flag_ident) {
                Some(self.#flag_field.contains(#flags_name::#flag_name))
            } else {
                None
            }
        ),
    }
}

pub fn generate_getters_setters(
    struct_item: &syn::ItemStruct,
    flags_name: &Ident,
    flag_field: &Ident,
    bool_fields: &[BoolField],
    args: &Args,
) -> TokenStream {
    let struct_name = &struct_item.ident;
    let (impl_generics, ty_generics, where_clause) = struct_item.generics.split_for_impl();

    let mut impl_body = TokenStream::new();
    for field in bool_fields {
        let field_cfgs = extract_cfgs(&field.attrs);
        let field_cfgs_clone = field_cfgs.clone();

        let field_docs = extract_docs(&field.attrs);
        let field_name = &field.field_ident;
        let flag_name = &field.flag_ident;

        let getter_vis = handle_visibility_arg(&field.vis, args.private_getters);
        let setter_vis = handle_visibility_arg(&field.vis, args.private_setters);
        let (setter_self_ty, setter_ret_ty, setter_ret) =
            handle_owning_setters(args.owning_setters);

        let (getter_name, setter_name) = args_to_names(args, field_name);
        let (getter_docs, setter_docs) = if args.document_setters {
            (TokenStream::default(), field_docs)
        } else {
            let setter_docs = format!("Sets the {field_name} to the value provided.");
            (field_docs, quote!(#[doc = #setter_docs]))
        };

        let getter_body = generate_getter_body(field, flag_field, flags_name);
        let to_extend = match field {
            BoolField::Normal(_) => quote!(
                #getter_docs
                #(#field_cfgs)*
                #getter_vis fn #getter_name(&self) -> bool {
                    #getter_body
                }

                #setter_docs
                #(#field_cfgs_clone)*
                #setter_vis fn #setter_name(#setter_self_ty, value: bool) -> #setter_ret_ty {
                    self.#flag_field.set(#flags_name::#flag_name, value);
                    #setter_ret
                }
            ),
            BoolField::Opt {
                tag_bit_flag_ident, ..
            } => quote!(
                #getter_docs
                #(#field_cfgs)*
                #getter_vis fn #getter_name(&self) -> Option<bool> {
                    #getter_body
                }

                #setter_docs
                #(#field_cfgs_clone)*
                #setter_vis fn #setter_name(#setter_self_ty, value: Option<bool>) -> #setter_ret_ty {
                    if let Some(value) = value {
                        self.#flag_field.insert(#flags_name::#tag_bit_flag_ident);
                        self.#flag_field.set(#flags_name::#flag_name, value);
                    } else {
                        self.#flag_field.remove(#flags_name::#tag_bit_flag_ident);
                    };
                    #setter_ret
                }
            ),
        };

        impl_body.extend([to_extend]);
    }

    quote!(
        impl #impl_generics #struct_name #ty_generics #where_clause {
            #impl_body
        }
    )
}