localsavefile_derive/
lib.rs

1extern crate proc_macro;
2
3use darling::{ast::NestedMeta, FromMeta};
4use proc_macro2::TokenStream;
5use quote::quote;
6use sanitize_filename::sanitize;
7use syn::{
8    punctuated::Punctuated, token::Comma, Data, DeriveInput, Fields, GenericParam, Generics, Ident,
9    ImplGenerics, WhereClause,
10};
11
12#[derive(Default, Debug, FromMeta)]
13struct LSFArgs {
14    version: Option<u32>,
15    path: Option<String>,
16    name: Option<String>,
17    persist: Option<bool>,
18}
19
20// https://github.com/avl/savefile/blob/1cce218a9fce5ee328e6ed0c77e020e53ef8e8d5/savefile-derive/src/common.rs#L6
21fn get_extra_where_clauses(
22    gen2: &Generics,
23    where_clause: Option<&WhereClause>,
24    the_trait: TokenStream,
25) -> TokenStream {
26    let extra_where_separator;
27    if let Some(where_clause) = where_clause {
28        if where_clause.predicates.trailing_punct() {
29            extra_where_separator = quote!();
30        } else {
31            extra_where_separator = quote!(,);
32        }
33    } else {
34        extra_where_separator = quote!(where);
35    }
36    let mut where_clauses = vec![];
37    for param in gen2.params.iter() {
38        if let GenericParam::Type(t) = param {
39            let t_name = &t.ident;
40            let clause = quote! {#t_name : #the_trait};
41            where_clauses.push(clause);
42        }
43    }
44    let extra_where = quote! {
45        #extra_where_separator #(#where_clauses),*
46    };
47    extra_where
48}
49
50fn impl_default(
51    input: &DeriveInput,
52    generics: &ImplGenerics,
53    name: &TokenStream,
54    extra_where: &TokenStream,
55    impl_common: &proc_macro2::TokenStream,
56    derives: &proc_macro2::TokenStream,
57) -> proc_macro::TokenStream {
58    // TODO: Add Savefile onto attrs
59    quote! {
60        #derives
61        #input
62
63        #impl_common
64
65        impl #generics ::localsavefile::LocalSaveFile for #name #extra_where {}
66    }
67    .into()
68}
69
70fn impl_persistent(
71    input: &DeriveInput,
72    generics: &ImplGenerics,
73    name: &TokenStream,
74    extra_where: &TokenStream,
75    impl_common: &proc_macro2::TokenStream,
76    derives: &proc_macro2::TokenStream,
77) -> proc_macro::TokenStream {
78    let attrs = &input.attrs;
79
80    match &input.data {
81        Data::Struct(data_struct) => {
82            let fields = &data_struct.fields;
83            let additional_field = quote! {
84                #[savefile_ignore]
85                #[savefile_introspect_ignore]
86                __place_localsavefile_above_any_derives: ::localsavefile::LocalSaveFileMetaData,
87            };
88
89            let new_fields = match fields {
90                Fields::Named(ref named_fields) => {
91                    let named = &named_fields.named;
92                    let named = named.iter().collect::<Punctuated<_, Comma>>();
93                    quote! {
94                        {
95                            #named,
96                            #additional_field
97                        }
98                    }
99                }
100                Fields::Unnamed(ref unnamed_fields) => {
101                    let unnamed = &unnamed_fields.unnamed;
102                    let unnamed = unnamed.iter().collect::<Punctuated<_, Comma>>();
103                    quote! {
104                        (
105                            #unnamed,
106                            #additional_field
107                        )
108                    }
109                }
110                Fields::Unit => {
111                    quote! {
112                        {
113                            #additional_field
114                        }
115                    }
116                }
117            };
118
119            let struct_attrs = attrs.iter();
120
121            // TODO: Add Savefile onto attrs
122            quote! {
123                #(#struct_attrs)*
124                #derives
125                struct #name #new_fields
126
127                #impl_common
128
129                impl #generics ::localsavefile::LocalSaveFilePersistent for #name #extra_where{
130                    fn get_metadata_mut(&mut self) -> &mut ::localsavefile::LocalSaveFileMetaData {
131                        &mut self.__place_localsavefile_above_any_derives
132                    }
133                }
134            }
135        }
136        _ => unimplemented!("LocalSaveFile can only be used with structs"),
137    }
138    .into()
139}
140
141fn localsavefile_main(
142    args: proc_macro::TokenStream,
143    input: proc_macro::TokenStream,
144    with_derives: bool,
145) -> proc_macro::TokenStream {
146    let input: DeriveInput = syn::parse_macro_input!(input);
147    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
148        Ok(v) => v,
149        Err(e) => {
150            return proc_macro::TokenStream::from(darling::Error::from(e).write_errors());
151        }
152    };
153    let args = match LSFArgs::from_list(&attr_args) {
154        Ok(v) => v,
155        Err(e) => {
156            return proc_macro::TokenStream::from(e.write_errors());
157        }
158    };
159
160    let name: &Ident = &input.ident;
161    let name_str = sanitize(name.to_string());
162    let version = args.version.unwrap_or(0);
163    let path: Option<String> = args.path;
164    let persist = args.persist.unwrap_or(false);
165    let struct_name = args.name;
166
167    let get_dir_path = match path {
168        Some(path) => quote! {
169            fn get_dir_path() -> ::std::io::Result<::std::path::PathBuf> {
170                Ok(::std::path::PathBuf::from(#path))
171            }
172        },
173        None => quote! {},
174    };
175
176    let derives = if with_derives {
177        quote! {#[derive(::savefile::prelude::Savefile, ::core::default::Default)]}
178    } else {
179        quote! {}
180    };
181
182    let struct_name = if let Some(struct_name) = struct_name {
183        let struct_name = sanitize(struct_name);
184        quote! {#struct_name.to_string()}
185    } else {
186        quote! {
187            let mut s = module_path!().replace("::", ".") + "." + #name_str;
188            s.make_ascii_lowercase();
189            s.retain(|c| !c.is_whitespace());
190            ::localsavefile::sanitize(s)
191        }
192    };
193
194    let generics = &input.generics;
195    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
196    let extra_where = get_extra_where_clauses(
197        generics,
198        where_clause,
199        quote! {::savefile::prelude::Packed + ::std::default::Default + ::savefile::Serialize + ::savefile::Deserialize},
200    );
201    let for_name = quote! {#name #ty_generics #where_clause};
202
203    let impl_common = quote! {
204        impl #impl_generics ::localsavefile::LocalSaveFileCommon for #for_name #extra_where {
205            fn get_version() -> u32 {
206                #version
207            }
208
209            fn get_struct_name() -> String {
210                #struct_name
211            }
212
213            fn get_pkg_name() -> String {
214                let mut s = std::env::var("LOCAL_SAVE_FILE_CARGO_PKG_NAME")
215                .unwrap_or(env!("CARGO_PKG_NAME").to_string());
216                s.make_ascii_lowercase();
217                s.retain(|c| !c.is_whitespace());
218                ::localsavefile::sanitize(s)
219            }
220
221            fn get_pkg_author() -> String {
222                let mut s = std::env::var("LOCAL_SAVE_FILE_CARGO_PKG_AUTHORS")
223                    .unwrap_or(env!("CARGO_PKG_AUTHORS").to_string());
224                let mut s = s.split(',').collect::<Vec<&str>>()[0].to_string();
225                s.make_ascii_lowercase();
226                s.retain(|c| !c.is_whitespace());
227                ::localsavefile::sanitize(s)
228            }
229
230            #get_dir_path
231        }
232    };
233
234    if persist {
235        impl_persistent(
236            &input,
237            &impl_generics,
238            &for_name,
239            &extra_where,
240            &impl_common,
241            &derives,
242        )
243    } else {
244        impl_default(
245            &input,
246            &impl_generics,
247            &for_name,
248            &extra_where,
249            &impl_common,
250            &derives,
251        )
252    }
253}
254
255#[proc_macro_attribute]
256pub fn localsavefile_impl(
257    args: proc_macro::TokenStream,
258    input: proc_macro::TokenStream,
259) -> proc_macro::TokenStream {
260    localsavefile_main(args, input, false)
261}
262
263#[proc_macro_attribute]
264pub fn localsavefile(
265    args: proc_macro::TokenStream,
266    input: proc_macro::TokenStream,
267) -> proc_macro::TokenStream {
268    localsavefile_main(args, input, true)
269}