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}