thag_proc_macros 0.2.0

Procedural macros for the thag_rs and thag_profiler crates
Documentation
#![allow(clippy::module_name_repetitions)]
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};

pub fn palette_methods_impl(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let fields = match input.data {
        Data::Struct(ref data) => match data.fields {
            Fields::Named(ref fields) => &fields.named,
            _ => panic!("PaletteMethods only works with named fields"),
        },
        _ => panic!("PaletteMethods only works with structs"),
    };

    let validation_calls = fields.iter().map(|f| {
        let field_name = &f.ident;
        quote! {
            validate_style(&self.#field_name, min_support)?;
        }
    });

    let conversion_fields = fields.iter().map(|f| {
        let field_name = &f.ident;
        quote! {
            #field_name: Style::from_config(&config.#field_name)?
        }
    });

    // New: Generate style references for iterator
    let style_refs = fields.iter().map(|f| {
        let field_name = &f.ident;
        quote! {
            &mut self.#field_name
        }
    });

    // Generate style name and reference pairs for regular iterator
    let style_name_refs = fields.iter().map(|f| {
        let field_name = &f.ident;
        let field_name_str = field_name.as_ref().unwrap().to_string();

        // Convert snake_case to Title case (e.g., "heading_1" -> "Heading1")
        let title_case = field_name_str
            .split('_')
            .map(|word| {
                let mut chars = word.chars();
                chars.next().map_or_else(String::new, |first| {
                    first.to_uppercase().collect::<String>() + chars.as_str()
                })
            })
            .collect::<String>();

        quote! {
            (#title_case, &self.#field_name)
        }
    });

    let output = quote! {
        impl Palette {
            /// Validates all styles in the palette against the minimum color support level.
            ///
            /// # Arguments
            /// * `min_support` - The minimum color support level required by the theme
            ///
            /// # Returns
            /// * `Ok(())` if all styles are valid for the given support level
            /// * `Err(ThemeError)` if any style requires higher color support than available
            pub fn validate_styles(&self, min_support: ColorSupport) -> ThagResult<()> {
                #(#validation_calls)*
                Ok(())
            }

            /// Creates a new Palette from a PaletteConfig
            ///
            /// Converts all StyleConfig entries to their corresponding Style values
            ///
            /// # Arguments
            /// * `config` - The PaletteConfig containing the style definitions
            ///
            /// # Returns
            /// * `Ok(Palette)` if all conversions succeed
            /// * `Err(ThemeError)` if any conversion fails
            pub fn from_config(config: &PaletteConfig) -> ThagResult<Self> {
                Ok(Self {
                    #(#conversion_fields,)*
                })
            }

            /// Get mutable iterator over all styles
            pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Style> {
                 vec![
                     #(#style_refs,)*
                 ].into_iter()
            }

            /// Get iterator over all styles with their names
            ///
            /// Returns an iterator of tuples where the first element is the style name
            /// in Title case (e.g., "Heading1") and the second element is a reference to the Style
            pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Style)> {
                vec![
                    #(#style_name_refs,)*
                ].into_iter()
            }
        }
    };

    output.into()
}