localsavefile_derive/
lib.rs1extern 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
20fn 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 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 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}