1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::parse::{Parse, ParseStream};
4use syn::{braced, Expr, Ident, Token, Type};
5
6#[derive(Clone)]
7struct ConfigInput {
8 root: StructDef,
9}
10
11#[derive(Clone)]
12struct StructDef {
13 name: Ident,
14 fields: Vec<Field>,
15}
16
17#[derive(Clone)]
18enum Field {
19 Leaf {
20 name: Ident,
21 ty: Box<Type>,
22 default: Option<Expr>,
23 },
24 Nested {
25 name: Ident,
26 fields: Vec<Field>,
27 },
28}
29
30impl Parse for ConfigInput {
31 fn parse(input: ParseStream) -> syn::Result<Self> {
32 let root: StructDef = input.parse()?;
33 Ok(ConfigInput { root })
34 }
35}
36
37impl Parse for StructDef {
38 fn parse(input: ParseStream) -> syn::Result<Self> {
39 let name: Ident = input.parse()?;
40 let content;
41 braced!(content in input);
42 let mut fields = Vec::new();
43 while !content.is_empty() {
44 fields.push(content.parse::<Field>()?);
45 if content.is_empty() {
46 break;
47 }
48 let _ = content.parse::<Token![,]>();
49 }
50 Ok(StructDef { name, fields })
51 }
52}
53
54impl Parse for Field {
55 fn parse(input: ParseStream) -> syn::Result<Self> {
56 let name: Ident = input.parse()?;
57 let _: Token![:] = input.parse()?;
58
59 if input.peek(syn::token::Brace) {
60 let content;
61 braced!(content in input);
62 let mut fields = Vec::new();
63 while !content.is_empty() {
64 fields.push(content.parse::<Field>()?);
65 if content.is_empty() {
66 break;
67 }
68 let _ = content.parse::<Token![,]>();
69 }
70 Ok(Field::Nested { name, fields })
71 } else {
72 let ty: Box<Type> = Box::new(input.parse()?);
73 let default = if input.peek(Token![=]) {
74 let _: Token![=] = input.parse()?;
75 Some(input.parse::<Expr>()?)
76 } else {
77 None
78 };
79 Ok(Field::Leaf { name, ty, default })
80 }
81 }
82}
83
84fn pascal_case(name: &Ident) -> String {
85 let s = name.to_string();
86 let mut result = String::with_capacity(s.len());
87 let mut capitalize = true;
88 for c in s.chars() {
89 if c == '_' {
90 capitalize = true;
91 } else if capitalize {
92 result.push(c.to_ascii_uppercase());
93 capitalize = false;
94 } else {
95 result.push(c);
96 }
97 }
98 result
99}
100
101fn gen_structs(def: &StructDef, _parent_name: &Ident) -> proc_macro2::TokenStream {
102 let struct_name = &def.name;
103 let field_defs: Vec<_> = def
104 .fields
105 .iter()
106 .map(|f| gen_field(f, struct_name))
107 .collect();
108 let struct_fields: Vec<_> = field_defs.iter().map(|f| &f.0).collect();
109 let default_fields: Vec<_> = field_defs.iter().map(|f| &f.1).collect();
110 let nested_structs: Vec<_> = def
111 .fields
112 .iter()
113 .filter_map(|f| {
114 if let Field::Nested { name, fields } = f {
115 let nested_name = format_ident!(
116 "{}{}",
117 struct_name,
118 pascal_case(name)
119 );
120 let nested_def = StructDef {
121 name: nested_name,
122 fields: fields.clone(),
123 };
124 Some(gen_structs(&nested_def, struct_name))
125 } else {
126 None
127 }
128 })
129 .collect();
130
131 quote! {
132 #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
133 pub struct #struct_name {
134 #(#struct_fields),*
135 }
136
137 impl Default for #struct_name {
138 fn default() -> Self {
139 Self {
140 #(#default_fields),*
141 }
142 }
143 }
144
145 #(#nested_structs)*
146 }
147}
148
149fn gen_field(field: &Field, parent: &Ident) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
150 match field {
151 Field::Leaf {
152 name,
153 ty,
154 default,
155 } => {
156 let field_def = quote! { #[serde(default)] pub #name: #ty };
157 let default_val = match default {
158 Some(expr) => quote! { #expr },
159 None => quote! { Default::default() },
160 };
161 let default_def = quote! { #name: #default_val };
162 (field_def, default_def)
163 }
164 Field::Nested { name, .. } => {
165 let nested_type = format_ident!("{}{}", parent, pascal_case(name));
166 let field_def = quote! { #[serde(default)] pub #name: #nested_type };
167 let default_def = quote! { #name: #nested_type::default() };
168 (field_def, default_def)
169 }
170 }
171}
172
173fn gen_load_method(root: &StructDef) -> proc_macro2::TokenStream {
174 let name = &root.name;
175 quote! {
176 impl #name {
177 pub fn load(path: impl AsRef<std::path::Path>) -> Result<Self, helium::ConfigError> {
184 match std::fs::read_to_string(path.as_ref()) {
185 Ok(content) => {
186 serde_json::from_str(&content).map_err(helium::ConfigError::parsing)
187 }
188 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
189 if let Some(parent) = path.as_ref().parent() {
190 let _ = std::fs::create_dir_all(parent);
191 }
192 let default = Self::default();
193 if let Ok(json) = serde_json::to_string_pretty(&default) {
194 let _ = std::fs::write(path.as_ref(), json);
195 }
196 Ok(default)
197 }
198 Err(e) => Err(helium::ConfigError::reading(e)),
199 }
200 }
201
202 pub fn reload(&mut self, path: impl AsRef<std::path::Path>) -> Result<(), helium::ConfigError> {
204 let content = std::fs::read_to_string(path.as_ref()).map_err(helium::ConfigError::reading)?;
205 *self = serde_json::from_str(&content).map_err(helium::ConfigError::parsing)?;
206 Ok(())
207 }
208
209 pub fn save(&self, path: impl AsRef<std::path::Path>) -> Result<(), helium::ConfigError> {
211 if let Some(parent) = path.as_ref().parent() {
212 std::fs::create_dir_all(parent).map_err(helium::ConfigError::reading)?;
213 }
214 let content = serde_json::to_string_pretty(self).map_err(helium::ConfigError::parsing)?;
215 std::fs::write(path.as_ref(), content).map_err(helium::ConfigError::reading)?;
216 Ok(())
217 }
218 }
219 }
220}
221
222#[proc_macro]
223pub fn helium_config(input: TokenStream) -> TokenStream {
224 let config: ConfigInput = syn::parse_macro_input!(input);
225 let root = &config.root;
226
227 let structs = gen_structs(root, &root.name);
228 let load = gen_load_method(root);
229
230 let expanded = quote! {
231 #structs
232 #load
233 };
234
235 TokenStream::from(expanded)
236}
237
238struct StructInput {
240 name: Ident,
241 fields: Vec<(Ident, Box<Type>)>,
242}
243
244impl Parse for StructInput {
245 fn parse(input: ParseStream) -> syn::Result<Self> {
246 let name: Ident = input.parse()?;
247 let content;
248 braced!(content in input);
249 let mut fields = Vec::new();
250 while !content.is_empty() {
251 let field_name: Ident = content.parse()?;
252 let _: Token![:] = content.parse()?;
253 let ty: Box<Type> = Box::new(content.parse()?);
254 fields.push((field_name, ty));
255 if content.is_empty() {
256 break;
257 }
258 let _ = content.parse::<Token![,]>();
259 }
260 Ok(StructInput { name, fields })
261 }
262}
263
264#[proc_macro]
278pub fn helium_struct(input: TokenStream) -> TokenStream {
279 let input: StructInput = syn::parse_macro_input!(input);
280 let name = &input.name;
281 let field_names: Vec<_> = input.fields.iter().map(|(n, _)| n).collect();
282 let field_tys: Vec<_> = input.fields.iter().map(|(_, t)| t).collect();
283
284 let expanded = quote! {
285 #[derive(Debug, Clone, PartialEq)]
286 pub struct #name {
287 #(pub #field_names: #field_tys),*
288 }
289
290 impl #name {
291 pub fn new(#(#field_names: #field_tys),*) -> Self {
293 Self { #(#field_names),* }
294 }
295 }
296 };
297
298 TokenStream::from(expanded)
299}
300
301struct ModelInput {
303 name: Ident,
304 ty: Box<Type>,
305}
306
307impl Parse for ModelInput {
308 fn parse(input: ParseStream) -> syn::Result<Self> {
309 let name: Ident = input.parse()?;
310 let content;
311 syn::parenthesized!(content in input);
312 let ty: Box<Type> = Box::new(content.parse()?);
313 Ok(ModelInput { name, ty })
314 }
315}
316
317#[proc_macro]
327pub fn helium_model(input: TokenStream) -> TokenStream {
328 let input: ModelInput = syn::parse_macro_input!(input);
329 let name = &input.name;
330 let ty = &input.ty;
331
332 let expanded = quote! {
333 #[derive(Debug)]
334 pub struct #name {
335 inner: slint::VecModel<#ty>,
336 }
337
338 impl #name {
339 pub fn new() -> Self {
341 Self { inner: slint::VecModel::new() }
342 }
343
344 pub fn from_vec(data: Vec<#ty>) -> Self {
346 Self { inner: slint::VecModel::from(data) }
347 }
348
349 pub fn push(&self, value: #ty) {
351 self.inner.push(value);
352 }
353
354 pub fn set_vec(&self, data: Vec<#ty>) {
356 self.inner.set_vec(data);
357 }
358
359 pub fn clear(&self) {
361 self.inner.clear();
362 }
363
364 pub fn row_count(&self) -> usize {
366 self.inner.row_count()
367 }
368 }
369
370 impl Default for #name {
371 fn default() -> Self {
372 Self::new()
373 }
374 }
375
376 impl From<Vec<#ty>> for #name {
377 fn from(data: Vec<#ty>) -> Self {
378 Self::from_vec(data)
379 }
380 }
381
382 impl From<#name> for slint_interpreter::Value {
383 fn from(model: #name) -> Self {
384 let model_rc: slint::ModelRc<#ty> = model.inner.into();
385 model_rc.into()
386 }
387 }
388 };
389
390 TokenStream::from(expanded)
391}