worktable_macros 0.9.0

Proc-macro companion crate for worktable: the worktable! macro and its derives. Formerly published as worktable_codegen.
Documentation
mod info;
mod usual;

use crate::common::name_generator::{WorktableNameGenerator, is_float, is_unsized};
use crate::generators::read_only::ReadOnlyGenerator;
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::quote;

impl ReadOnlyGenerator {
    pub fn gen_index_def(&mut self) -> syn::Result<TokenStream> {
        let type_def = self.gen_type_def()?;
        let impl_def = self.gen_secondary_index_impl_def();
        let info_def = self.gen_secondary_index_info_impl_def();
        let default_impl = self.gen_index_default_impl()?;
        let available_indexes = self.gen_available_indexes();

        Ok(quote! {
            #type_def
            #impl_def
            #info_def
            #default_impl
            #available_indexes
        })
    }

    fn gen_type_def(&mut self) -> syn::Result<TokenStream> {
        let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string());
        let ident = name_generator.get_index_type_ident();
        let index_rows = self
            .columns
            .indexes
            .iter()
            .map(|(i, idx)| {
                let Some(t) = self.columns.columns_map.get(i) else {
                    return Err(syn::Error::new(
                        i.span(),
                        format!("cannot find column `{i}` in this table"),
                    ));
                };
                let t = if is_float(t.to_string().as_str()) {
                    quote! { OrderedFloat<#t> }
                } else {
                    quote! { #t }
                };
                let i = &idx.name;

                #[allow(clippy::collapsible_else_if)]
                let res = if idx.is_unique {
                    if is_unsized(&t.to_string()) {
                        quote! {
                            #i: IndexMap<#t, OffsetEqLink, UnsizedNode<IndexPair<#t, OffsetEqLink>>>
                        }
                    } else {
                        quote! {#i: IndexMap<#t, OffsetEqLink>}
                    }
                } else {
                    if is_unsized(&t.to_string()) {
                        quote! {#i: IndexMultiMap<#t, OffsetEqLink, UnsizedNode<IndexMultiPair<#t, OffsetEqLink>>>}
                    } else {
                        quote! {#i: IndexMultiMap<#t, OffsetEqLink>}
                    }
                };
                Ok::<_, syn::Error>(res)
            })
            .collect::<Result<Vec<_>, syn::Error>>()?;

        let derive = quote! {
            #[derive(Debug, MemStat, PersistIndex)]
            #[index(read_only)]
        };

        Ok(quote! {
            #derive
            pub struct #ident {
                #(#index_rows),*
            }
        })
    }

    fn gen_index_default_impl(&self) -> syn::Result<TokenStream> {
        let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string());
        let index_type_ident = name_generator.get_index_type_ident();
        let const_name = name_generator.get_page_inner_size_const_ident();

        let index_rows = self
            .columns
            .indexes
            .iter()
            .map(|(i, idx)| {
                let Some(t) = self.columns.columns_map.get(i) else {
                    return Err(syn::Error::new(
                        i.span(),
                        format!("cannot find column `{i}` in this table"),
                    ));
                };
                let t = if is_float(t.to_string().as_str()) {
                    quote! { OrderedFloat<#t> }
                } else {
                    quote! { #t }
                };
                let i = &idx.name;

                #[allow(clippy::collapsible_else_if)]
                let res = if idx.is_unique {
                    if is_unsized(&t.to_string()) {
                        quote! {
                            #i: IndexMap::with_maximum_node_size(#const_name),
                        }
                    } else {
                        quote! {#i: IndexMap::with_maximum_node_size(get_index_page_size_from_data_length::<#t>(#const_name)),}
                    }
                } else {
                    if is_unsized(&t.to_string()) {
                        quote! {#i: IndexMultiMap::with_maximum_node_size(#const_name), }
                    } else {
                        quote! {#i: IndexMultiMap::with_maximum_node_size(get_index_page_size_from_data_length::<#t>(#const_name)),}
                    }
                };

                Ok::<_, syn::Error>(res)
            })
            .collect::<Result<Vec<_>, syn::Error>>()?;

        Ok(quote! {
            impl Default for #index_type_ident {
                fn default() -> Self {
                    Self {
                        #(#index_rows)*
                    }
                }
            }
        })
    }

    fn gen_available_indexes(&self) -> TokenStream {
        let name_generator = WorktableNameGenerator::from_table_name(self.name.to_string());
        let avt_type_ident = name_generator.get_available_indexes_ident();

        let indexes = self.columns.indexes.values().map(|i| {
            let camel_case_name = i.name.to_string().from_case(Case::Snake).to_case(Case::Pascal);
            let i: TokenStream = camel_case_name.parse().unwrap();
            quote! {
                #i,
            }
        });

        if self.columns.indexes.is_empty() {
            quote! {
                pub type #avt_type_ident = ();
            }
        } else {
            quote! {
                #[derive(Debug, Clone, Copy, MoreDisplay, PartialEq, PartialOrd, Ord, Hash, Eq)]
                pub enum #avt_type_ident {
                    #(#indexes)*
                }

                impl AvailableIndex for #avt_type_ident {
                    fn to_string_value(&self) -> String {
                        ToString::to_string(&self)
                    }
                }
            }
        }
    }
}