1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#![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()
}