binroots_proc_macros/
lib.rs

1use convert_case::{Case, Casing};
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput};
4
5/// # binroots_enum
6/// A procedural macro attribute that enables serialization, default and debug operations for an enum.
7/// Usage
8/// `#[binroots_enum]` can be applied to enums. The attribute generates an implementation of [`binroots::Serialize`][brserialize] (re-exported from serde::Serialize), and attempts to automatically pick the default value.
9/// ```rust
10/// use binroots::binroots_enum;
11///
12/// #[binroots_enum]
13/// pub enum MyEnum {
14///     VariantA,
15///     VariantB,
16///     #[default]
17///     VariantC,
18/// }
19/// ```
20/// Notice how we're using `#[default]` to mark the default variant of `MyEnum`. It's also possible for `#[binroots_enum]` to automatically pick the default variant:
21/// ```
22/// use binroots::binroots_enum;
23///
24/// #[binroots_enum]
25/// pub enum MyEnum {
26///     None, // Automatically chosen to be the default value
27///           // #[binroots_enum] automatically picks the first instance of either "None", "Nothing", "Default" or "Empty" to be default.
28///     VariantA,
29///     VariantB,
30/// }
31///
32/// #[binroots_enum(manual)]
33/// pub enum MyManualEnum {
34///     None, // Not selected as the default variant because we use the `manual` annotation
35///     #[default]
36///     VariantA,
37///     VariantB
38/// }
39/// ```
40/// The generated code includes a new implementation of the input struct with the following changes:
41///     - `derive`s [`Debug`], [`Default`], [`binroots::Serialize`][brserialize]
42///     - Adds a `new` method to the struct, which constructs a new instance of the struct from its fields.
43///     - A `#[default]` marker inserted wherever possible, overrided by the `manual` annotation
44// Example
45/// ```rust
46/// use binroots::binroots_enum;
47/// use binroots::save::{RootType, Save};
48///
49/// #[binroots_enum]
50/// pub enum Activity {
51///     Nothing,
52///     Playing(String),
53///     Watching(String),
54/// }
55///
56/// fn main() {
57///     let activity = Activity::Playing("bideo games".into());
58///
59///     activity.save("activity", RootType::InMemory).unwrap(); // Saves the enum to the disk
60/// }
61/// ```
62/// [brserialize]: https://docs.rs/binroots/latest/binroots/trait.Serialize.html
63#[proc_macro_attribute]
64pub fn binroots_enum(
65    attr: proc_macro::TokenStream,
66    item: proc_macro::TokenStream,
67) -> proc_macro::TokenStream {
68    let input = parse_macro_input!(item as DeriveInput);
69    let vis = input.vis;
70    let ident = input.ident;
71    let attrs = input.attrs;
72    let generics = input.generics;
73
74    let mut found = None;
75    let mut manual = false;
76
77    for a in attr {
78        manual = a.to_string() == "manual"
79    }
80
81    let variants = if let syn::Data::Enum(syn::DataEnum { variants, .. }) = input.data {
82        if manual {
83            variants.into_iter().map(|v| quote!(#v)).collect::<Vec<_>>()
84        } else {
85            variants
86                .into_iter()
87                .enumerate()
88                .map(|(i, v)| {
89                    if (v.ident == "None"
90                        || v.ident == "Nothing"
91                        || v.ident == "Empty"
92                        || v.ident == "Default")
93                        && found.is_none()
94                    {
95                        found = Some(i)
96                    }
97
98                    if let Some(c) = found {
99                        if c == i {
100                            quote! {
101                                #[default]
102                                #v
103                            }
104                        } else {
105                            quote!(#v)
106                        }
107                    } else {
108                        quote!(#v)
109                    }
110                })
111                .collect::<Vec<_>>()
112        }
113    } else {
114        panic!("#[binroots_enum] only supports enums.")
115    };
116
117    let output = quote! {
118        #[derive(Debug, Default, binroots::Serialize)]
119        #( #attrs )*
120        #vis enum #ident #generics {
121            #(
122
123                #variants
124            ),*
125        }
126    };
127
128    output.into()
129}
130
131/// # binroots_struct
132/// A procedural macro attribute that enables serialization and structured file-saving for it and its fields.
133/// Usage
134/// `#[binroots_struct]` can be applied to any named struct with named fields. The attribute generates an implementation of [`binroots::Serialize`][brserialize] (re-exported from serde::Serialize) for the struct, as well as a set of methods for serializing, deserializing, and saving instances of the struct to disk.
135/// ```rust
136/// use binroots::binroots_struct;
137///
138/// #[binroots_struct] // Saves to `/tmp/<CARGO_PKG_NAME>/my-struct/` on Unix
139/// pub struct MyStruct {
140///     field1: i32,
141///     field2: String,
142///     field3: bool,
143/// }
144///
145/// // --- OR ---
146///
147/// #[binroots_struct(persistent)] // Saves to `$HOME/.cache/<CARGO_PKG_NAME>/my-persistent-struct/` on Unix
148/// pub struct MyPersistentStruct {
149///     field1: i32,
150///     field2: String,
151///     field3: bool,
152/// }
153/// ```
154/// The generated code includes a new implementation of the input struct with the following changes:
155///     - Wraps each field in a [`binroots::field::BinrootsField`][brfield]
156///     - `derive`s [`Debug`] and [`binroots::Serialize`][brserialize]
157///     - Generates `Self::ROOT_FOLDER`, the kebab-case name of the struct
158///     - Adds a `new` method to the struct, which constructs a new instance of the struct from its fields.
159///     - Adds a `save` method to the struct, which serializes the struct and saves it to disk using the [`binroots::save::Save`][brsave] trait, saving to `Self::ROOT_FOLDER`.
160///     - A [`Default`] implementation is added to the struct, which constructs a default instance of the struct with default values for all fields.
161///
162// Example
163/// ```rust
164/// use binroots::binroots_struct;
165/// use binroots::save::RootType;
166///
167/// #[binroots_struct] // Root save path on Unix is `/tmp/<CARGO_PKG_NAME>/person/` because we didn't annotate with `#[binroots_struct(persistent)]`
168/// pub struct Person {
169///     name: String,
170///     gender: String,
171///     age: u8,
172///     email: Option<String>,
173/// }
174///
175/// fn main() {
176///     let mut person = Person::new(
177///         "Alex".into(),
178///         "Male".into(),
179///         42,
180///         Some("alex@example.com".into()),
181///     );
182///
183///     // We need to dereference because `person.alice` is `binroots::field::BinrootsField<"name", u8>`
184///     *person.name = "Alice".into();
185///     *person.gender = "Female".into();
186///
187///     person.save().unwrap(); // Saves the entire struct to the disk
188///
189///     *person.email = Some("alice@example.com".into());
190///     person.email.save(Person::ROOT_FOLDER, RootType::InMemory).unwrap(); // Saves only person.email to the disk in its appropriate location
191/// }
192/// ```
193/// [brserialize]: https://docs.rs/binroots/latest/binroots/trait.Serialize.html
194/// [brfield]: https://docs.rs/binroots/latest/binroots/field/struct.BinrootsField.html
195/// [brsave]: https://docs.rs/binroots/latest/binroots/save/trait.Save.html
196/// [brrt]: https://docs.rs/binroots/latest/binroots/save/struct.RootType.html
197#[proc_macro_attribute]
198pub fn binroots_struct(
199    attr: proc_macro::TokenStream,
200    item: proc_macro::TokenStream,
201) -> proc_macro::TokenStream {
202    let input = parse_macro_input!(item as DeriveInput);
203    let struct_name = &input.ident;
204    let vis = &input.vis;
205
206    let mut root_type =
207        quote!(const ROOT_TYPE: binroots::save::RootType = binroots::save::RootType::InMemory);
208
209    for a in attr {
210        if a.to_string() == "persistent" {
211            root_type = quote!(const ROOT_TYPE: binroots::save::RootType = binroots::save::RootType::Persistent);
212        }
213    }
214
215    let fields = if let syn::Data::Struct(syn::DataStruct {
216        fields: syn::Fields::Named(ref fields),
217        ..
218    }) = input.data
219    {
220        &fields.named
221    } else {
222        panic!("#[binroots_struct] only supports named struct fields")
223    };
224
225    let field_names = fields.iter().map(|field| {
226        let field_name = &field.ident.as_ref().unwrap();
227        let field_name_str = &field.ident.as_ref().unwrap().to_string();
228        let field_type = &field.ty;
229
230        quote! {
231            #field_name: binroots::field::BinrootsField<#field_name_str, #field_type>,
232        }
233    });
234
235    let field_initializers_new = fields.iter().map(|field| {
236        let field_name = &field.ident.as_ref().unwrap();
237        let field_name_str = &field.ident.as_ref().unwrap().to_string();
238        let field_type = &field.ty;
239
240        quote! {
241            #field_name: binroots::field::BinrootsField::<#field_name_str, #field_type>::new(#field_name),
242        }
243    });
244
245    let field_initializers_default = fields.iter().map(|field| {
246        let field_name = &field.ident.as_ref().unwrap();
247        let field_name_str = &field.ident.as_ref().unwrap().to_string();
248        let field_type = &field.ty;
249
250        quote! {
251            #field_name: binroots::field::BinrootsField::<#field_name_str, #field_type>::default(),
252        }
253    });
254
255    let struct_name_str = struct_name.to_string().to_case(Case::Kebab);
256
257    let output = quote! {
258        #[derive(Debug, binroots::Serialize)]
259        #vis struct #struct_name {
260            #( #field_names )*
261        }
262
263        impl #struct_name {
264            const ROOT_FOLDER: &'static str = #struct_name_str;
265            #root_type;
266            pub fn new(#fields) -> Self {
267                Self {
268                    #( #field_initializers_new )*
269                }
270            }
271
272            pub fn save(&self) -> Result<(), binroots::save::SaveError> {
273                binroots::save::Save::save(self, Self::ROOT_FOLDER, Self::ROOT_TYPE)
274            }
275        }
276
277        impl Default for #struct_name {
278            fn default() -> Self {
279                Self {
280                    #( #field_initializers_default )*
281                }
282            }
283        }
284
285    };
286
287    output.into()
288}