1use proc_macro2::Ident;
2use proc_macro2::TokenStream;
3use quote::format_ident;
4use quote::quote;
5use syn::Field;
6use syn::ImplGenerics;
7use syn::ItemStruct;
8use syn::Token;
9use syn::Type;
10use syn::parse_quote;
11
12#[proc_macro_derive(DirStructure, attributes(dir_structure))]
13pub fn derive_dir_structure(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
14 let item = syn::parse_macro_input!(item as ItemStruct);
15
16 expand_dir_structure(item)
17 .unwrap_or_else(|err| err.to_compile_error())
18 .into()
19}
20
21struct DirStructureForField {
22 read_code: TokenStream,
23 async_read_code: TokenStream,
24 async_read_bound: Option<syn::WherePredicate>,
25 write_code: TokenStream,
26 async_write_code: TokenStream,
27 async_write_bound: Option<syn::WherePredicate>,
28 async_write_owned_code: TokenStream,
29 async_write_owned_bound: Option<syn::WherePredicate>,
30 #[cfg(feature = "resolve-path")]
31 has_field_impl: TokenStream,
32}
33
34fn expand_dir_structure_for_field(
35 (impl_generics, ty_name, ty_generics, where_clause): (
36 &ImplGenerics,
37 &Ident,
38 &syn::TypeGenerics,
39 Option<&syn::WhereClause>,
40 ),
41 path_param_name: &Ident,
42 field: &Field,
43) -> syn::Result<DirStructureForField> {
44 let field_name = field.ident.as_ref().ok_or_else(|| {
45 syn::Error::new_spanned(
46 field,
47 "DirStructure can only be derived for structs with named fields",
48 )
49 })?;
50
51 let field_ty = &field.ty;
52
53 enum PathData {
54 SelfPath,
55 Path(String),
56 None,
57 }
58
59 let mut path = PathData::None;
60 let mut self_path = field_name == "self_path";
61 let mut with_newtype = None::<Type>;
62
63 for attr in field
64 .attrs
65 .iter()
66 .filter(|attr| attr.meta.path().is_ident("dir_structure"))
67 {
68 attr.parse_nested_meta(|meta| {
69 if meta.path.is_ident("path") {
70 let _eq = meta.input.parse::<Token![=]>()?;
71 if meta.input.peek(syn::LitStr) {
72 let s = meta.input.parse::<syn::LitStr>()?;
73 path = PathData::Path(s.value());
74 } else if meta.input.peek(Token![self]) {
75 let _self = meta.input.parse::<Token![self]>()?;
76 path = PathData::SelfPath;
77 } else {
78 return Err(syn::Error::new_spanned(
79 meta.path,
80 "Expected a string literal or `self`",
81 ));
82 }
83 } else if meta.path.is_ident("self_path") {
84 self_path = true;
85 } else if meta.path.is_ident("with_newtype") {
86 let _eq = meta.input.parse::<Token![=]>()?;
87 let ty = meta.input.parse::<Type>()?;
88 with_newtype = Some(ty);
89 } else {
90 return Err(syn::Error::new_spanned(
91 meta.path,
92 "Unknown attribute for dir_structure",
93 ));
94 }
95
96 Ok(())
97 })?;
98 }
99
100 let (actual_path_expr, actual_path_expr_move, path_pusher_for_has_field) = match path {
101 PathData::Path(p) => (
102 quote! { #path_param_name.join(#p) },
103 quote! { #path_param_name.join(#p) },
104 quote! { #path_param_name.push(#p); },
105 ),
106 PathData::SelfPath => (
107 quote! { #path_param_name },
108 quote! { #path_param_name.clone() },
109 quote! {},
110 ),
111 PathData::None => {
112 let name = field_name.to_string();
113 (
114 quote! { #path_param_name.join(#name) },
115 quote! { #path_param_name.join(#name) },
116 quote! { #path_param_name.push(#name); },
117 )
118 }
119 };
120 let actual_field_ty_perform = with_newtype.as_ref().unwrap_or(field_ty);
121 let read_code = if self_path {
122 quote! {
124 #field_ty::from(#path_param_name)
125 }
126 } else {
127 let value_name = format_ident!("__value");
128 let end_expr = match &with_newtype {
129 Some(nt) => quote! {
130 <#nt as ::dir_structure::NewtypeToInner>::into_inner(#value_name)
131 },
132 None => quote! {
133 #value_name
134 },
135 };
136
137 quote! {{
138 let __translated_path = #actual_path_expr;
139 let #value_name = <#actual_field_ty_perform as ::dir_structure::ReadFrom>::read_from(&__translated_path)?;
140 #end_expr
141 }}
142 };
143
144 let async_read_code = if self_path {
145 quote! {
147 #field_ty::from(#path_param_name)
148 }
149 } else {
150 let value_name = format_ident!("__value");
151 let end_expr = match &with_newtype {
152 Some(nt) => quote! {
153 <#nt as ::dir_structure::NewtypeToInner>::into_inner(#value_name)
154 },
155 None => quote! {
156 #value_name
157 },
158 };
159
160 quote! {{
161 let __translated_path = #actual_path_expr_move;
162 let #value_name = <#actual_field_ty_perform as ::dir_structure::ReadFromAsync>::read_from_async(__translated_path).await?;
163 #end_expr
164 }}
165 };
166
167 let write_code = if self_path {
168 quote! {}
170 } else {
171 let writer = match &with_newtype {
172 Some(nt) => {
173 quote! { &<#nt as ::dir_structure::FromRefForWriter<'_>>::from_ref_for_writer(&self.#field_name) }
174 }
175 None => quote! { &self.#field_name },
176 };
177 quote! {
178 let __translated_path = #actual_path_expr;
179 ::dir_structure::WriteTo::write_to(#writer, &__translated_path)?;
180 }
181 };
182
183 let async_write_code = if self_path {
184 quote! {}
186 } else {
187 match &with_newtype {
188 Some(nt) => {
189 quote! {
190 let __translated_path = #actual_path_expr_move;
191 <<#nt as ::dir_structure::FromRefForWriterAsync<'_>>::Wr as ::dir_structure::WriteToAsyncOwned<'_>>::write_to_async_owned(<#nt as ::dir_structure::FromRefForWriterAsync<'_>>::from_ref_for_writer_async(&self.#field_name), __translated_path).await?;
192 }
193 }
194 None => quote! {
195 let __translated_path = #actual_path_expr_move;
196 <#actual_field_ty_perform as ::dir_structure::WriteToAsync>::write_to_async(&self.#field_name, __translated_path).await?;
197 },
198 }
199 };
200
201 let async_write_owned_code = if self_path {
202 quote! {}
204 } else {
205 quote! {
206 let __translated_path = #actual_path_expr_move;
207 ::dir_structure::WriteToAsyncOwned<'_>::write_to_async_owned(self.#field_name, __translated_path).await?;
208 }
209 };
210
211 #[cfg(feature = "resolve-path")]
212 let has_field_impl = if self_path {
213 quote! {}
214 } else {
215 use std::iter;
216
217 use crate::resolve_path::MAX_LEN;
218
219 let field_name_str = field_name.to_string();
220 if field_name_str.len() > MAX_LEN {
221 return Err(syn::Error::new_spanned(
222 field_name,
223 format!(
224 "Field name for DirStructure must be at most {} characters long",
225 MAX_LEN
226 ),
227 ));
228 }
229 let field_name_array: [char; MAX_LEN] = field_name_str
230 .chars()
231 .chain(iter::repeat('\0'))
232 .take(MAX_LEN)
233 .collect::<Vec<_>>()
234 .try_into()
235 .unwrap();
236
237 quote! {
238 impl #impl_generics ::dir_structure::HasField<{ [#(#field_name_array),*] }> for #ty_name #ty_generics #where_clause {
239 type Inner = #field_ty;
240
241 fn resolve_path(mut #path_param_name: ::std::path::PathBuf) -> ::std::path::PathBuf {
242 #path_pusher_for_has_field
243 #path_param_name
244 }
245 }
246 }
247 };
248 Ok(DirStructureForField {
249 read_code: quote! {
250 #field_name: #read_code
251 },
252 async_read_code: quote! {
253 #field_name: #async_read_code
254 },
255 async_read_bound: if self_path {
256 None
257 } else {
258 Some(parse_quote! {
259 for<'___trivial_bound> #actual_field_ty_perform: ::dir_structure::ReadFromAsync
260 })
261 },
262 write_code,
263 async_write_code,
264 async_write_bound: if self_path {
265 None
266 } else {
267 Some(parse_quote! {
268 for<'___trivial_bound> #actual_field_ty_perform: ::dir_structure::WriteToAsync
269 })
270 },
271 async_write_owned_code,
272 async_write_owned_bound: if self_path {
273 None
274 } else {
275 Some(parse_quote! {
276 for<'___trivial_bound> #actual_field_ty_perform: ::dir_structure::WriteToAsyncOwned<'_>
277 })
278 },
279 #[cfg(feature = "resolve-path")]
280 has_field_impl,
281 })
282}
283
284fn expand_dir_structure(st: ItemStruct) -> syn::Result<TokenStream> {
285 let name = &st.ident;
286 let path_param_name = format_ident!("__dir_structure_path");
287 let (impl_generics, ty_generics, where_clause) = st.generics.split_for_impl();
288
289 let mut field_read_impls = Vec::new();
290 let mut field_async_read_impls = Vec::new();
291 let mut field_async_read_bounds = Vec::new();
292 let mut field_write_impls = Vec::new();
293 let mut field_async_write_impls = Vec::new();
294 let mut field_async_write_bounds = Vec::new();
295 let mut field_async_write_owned_impls = Vec::new();
296 let mut field_async_write_owned_bounds = Vec::new();
297 #[cfg(feature = "resolve-path")]
298 let mut has_field_impls = Vec::new();
299
300 for field in &st.fields {
301 let DirStructureForField {
302 read_code,
303 async_read_code,
304 async_read_bound,
305 write_code,
306 async_write_code,
307 async_write_bound,
308 async_write_owned_code,
309 async_write_owned_bound,
310 #[cfg(feature = "resolve-path")]
311 has_field_impl,
312 } = expand_dir_structure_for_field(
313 (&impl_generics, name, &ty_generics, where_clause),
314 &path_param_name,
315 field,
316 )?;
317 field_read_impls.push(read_code);
318 field_async_read_impls.push(async_read_code);
319 field_async_read_bounds.extend(async_read_bound);
320 field_write_impls.push(write_code);
321 field_async_write_impls.push(async_write_code);
322 field_async_write_bounds.extend(async_write_bound);
323 field_async_write_owned_impls.push(async_write_owned_code);
324 field_async_write_owned_bounds.push(async_write_owned_bound);
325 #[cfg(feature = "resolve-path")]
326 has_field_impls.push(has_field_impl);
327 }
328
329 #[cfg_attr(
330 all(not(feature = "async"), not(feature = "resolve-path")),
331 expect(unused_mut)
332 )]
333 let mut expanded = quote! {
334 impl #impl_generics ::dir_structure::ReadFrom for #name #ty_generics #where_clause {
335 fn read_from(#path_param_name: &::std::path::Path) -> ::dir_structure::Result<Self>
336 where
337 Self: Sized,
338 {
339 Ok(Self {
340 #(#field_read_impls,)*
341 })
342 }
343 }
344 impl #impl_generics ::dir_structure::WriteTo for #name #ty_generics #where_clause {
345 fn write_to(&self, #path_param_name: &::std::path::Path) -> ::dir_structure::Result<()> {
346 #(#field_write_impls)*
347 Ok(())
348 }
349 }
350 impl #impl_generics ::dir_structure::DirStructure for #name #ty_generics #where_clause {}
351 };
352
353 #[cfg(feature = "async")]
354 {
355 use syn::punctuated::Punctuated;
356
357 fn merge_where_clause(
358 where_clause: Option<syn::WhereClause>,
359 additional_bounds: Vec<syn::WherePredicate>,
360 ) -> Option<syn::WhereClause> {
361 if let Some(mut where_clause) = where_clause {
362 where_clause.predicates.extend(additional_bounds);
363 Some(where_clause)
364 } else {
365 let mut where_clause = syn::WhereClause {
366 where_token: <Token![where]>::default(),
367 predicates: Punctuated::new(),
368 };
369 where_clause.predicates.extend(additional_bounds);
370 if where_clause.predicates.is_empty() {
371 None
372 } else {
373 Some(where_clause)
374 }
375 }
376 }
377
378 let where_clause_read_from_async =
379 merge_where_clause(where_clause.cloned(), field_async_read_bounds);
380 let where_clause_write_to_async =
381 merge_where_clause(where_clause.cloned(), field_async_write_bounds);
382 expanded.extend(quote! {
383 impl #impl_generics ::dir_structure::ReadFromAsync for #name #ty_generics #where_clause_read_from_async {
384 type Future = ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::dir_structure::Result<Self>> + ::std::marker::Send + 'static>>;
385
386 fn read_from_async(#path_param_name: ::std::path::PathBuf) -> Self::Future
387 where
388 Self: Sized,
389 {
390 Box::pin(async move {
391 Ok(Self {
392 #(#field_async_read_impls,)*
393 })
394 })
395 }
396 }
397 impl #impl_generics ::dir_structure::WriteToAsync for #name #ty_generics #where_clause_write_to_async {
398 type Future<'a> = ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::dir_structure::Result<()>> + ::std::marker::Send + 'a>>
399 where
400 Self: 'a;
401
402 fn write_to_async(&self, #path_param_name: ::std::path::PathBuf) -> Self::Future<'_> {
403 Box::pin(async move {
404 #(#field_async_write_impls)*
405 Ok(())
406 })
407 }
408 }
409 });
410 }
411
412 #[cfg(feature = "resolve-path")]
413 {
414 for has_field_impl in has_field_impls {
415 expanded.extend(has_field_impl);
416 }
417 }
418
419 Ok(expanded)
420}
421
422#[cfg(feature = "resolve-path")]
423mod resolve_path;
424
425#[cfg(feature = "resolve-path")]
426#[proc_macro]
427pub fn resolve_path(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
428 resolve_path::resolve_path(input)
429}
430
431#[cfg(feature = "resolve-path")]
432#[proc_macro]
433pub fn __resolve_max_len(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
434 let max_len = resolve_path::MAX_LEN;
437 let output = quote! { #max_len };
438 output.into()
439}