dir_structure_macros/
lib.rs1use proc_macro2::{Ident, TokenStream};
2use quote::{format_ident, quote};
3use syn::{Field, ItemStruct, Token, Type};
4
5#[proc_macro_derive(DirStructure, attributes(dir_structure))]
6pub fn derive_dir_structure(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
7 let item = syn::parse_macro_input!(item as ItemStruct);
8
9 expand_dir_structure(item)
10 .unwrap_or_else(|err| err.to_compile_error())
11 .into()
12}
13
14struct DirStructureForField {
15 read_code: TokenStream,
16 write_code: TokenStream,
17}
18
19fn expand_dir_structure_for_field(
20 path_param_name: &Ident,
21 field: &Field,
22) -> syn::Result<DirStructureForField> {
23 let field_name = field.ident.as_ref().ok_or_else(|| {
24 syn::Error::new_spanned(
25 field,
26 "DirStructure can only be derived for structs with named fields",
27 )
28 })?;
29
30 let field_ty = &field.ty;
31
32 enum PathData {
33 SelfPath,
34 Path(String),
35 None,
36 }
37
38 let mut path = PathData::None;
39 let mut self_path = field_name == "self_path";
40 let mut with_newtype = None::<Type>;
41
42 for attr in field
43 .attrs
44 .iter()
45 .filter(|attr| attr.meta.path().is_ident("dir_structure"))
46 {
47 attr.parse_nested_meta(|meta| {
48 if meta.path.is_ident("path") {
49 let _eq = meta.input.parse::<Token![=]>()?;
50 if meta.input.peek(syn::LitStr) {
51 let s = meta.input.parse::<syn::LitStr>()?;
52 path = PathData::Path(s.value());
53 } else if meta.input.peek(Token![self]) {
54 let _self = meta.input.parse::<Token![self]>()?;
55 path = PathData::SelfPath;
56 } else {
57 return Err(syn::Error::new_spanned(
58 meta.path,
59 "Expected a string literal or `self`",
60 ));
61 }
62 } else if meta.path.is_ident("self_path") {
63 self_path = true;
64 } else if meta.path.is_ident("with_newtype") {
65 let _eq = meta.input.parse::<Token![=]>()?;
66 let ty = meta.input.parse::<Type>()?;
67 with_newtype = Some(ty);
68 } else {
69 return Err(syn::Error::new_spanned(
70 meta.path,
71 "Unknown attribute for dir_structure",
72 ));
73 }
74
75 Ok(())
76 })?;
77 }
78
79 let actual_path_expr = match path {
80 PathData::Path(p) => quote! {#path_param_name.join(#p)},
81 PathData::SelfPath => quote! { #path_param_name },
82 PathData::None => {
83 let name = field_name.to_string();
84 quote! {#path_param_name.join(#name)}
85 }
86 };
87 let actual_field_ty_perform = with_newtype.as_ref().unwrap_or(field_ty);
88 let read_code = if self_path {
89 quote! {
90 #field_ty::from(#path_param_name)
91 }
92 } else {
93 let value_name = format_ident!("__value");
94 let end_expr = match &with_newtype {
95 Some(nt) => quote! {
96 <#nt as ::dir_structure::NewtypeToInner>::into_inner(#value_name)
97 },
98 None => quote! {
99 #value_name
100 },
101 };
102
103 quote! {{
104 let __translated__path = #actual_path_expr;
105 let #value_name = <#actual_field_ty_perform as ::dir_structure::ReadFrom>::read_from(&__translated__path)?;
106 #end_expr
107 }}
108 };
109
110 let write_code = if self_path {
111 quote! {}
112 } else {
113 let writer = match &with_newtype {
114 Some(nt) => {
115 quote! { &<#nt as ::dir_structure::FromRefForWriter<'_>>::from_ref_for_writer(&self.#field_name) }
116 }
117 None => quote! { &self.#field_name },
118 };
119 quote! {
120 let __translated_path = #actual_path_expr;
121 ::dir_structure::WriteTo::write_to(#writer, &__translated_path)?;
122 }
123 };
124
125 Ok(DirStructureForField {
126 read_code: quote! {
127 #field_name: #read_code
128 },
129 write_code,
130 })
131}
132
133fn expand_dir_structure(st: ItemStruct) -> syn::Result<TokenStream> {
134 let name = &st.ident;
135 let path_param_name = format_ident!("__DIR_STRUCTURE_PATH");
136 let (impl_generics, ty_generics, where_clause) = st.generics.split_for_impl();
137
138 let mut field_read_impls = Vec::new();
139 let mut field_write_impls = Vec::new();
140
141 for field in &st.fields {
142 let DirStructureForField {
143 read_code,
144 write_code,
145 } = expand_dir_structure_for_field(&path_param_name, field)?;
146 field_read_impls.push(read_code);
147 field_write_impls.push(write_code);
148 }
149
150 let expanded = quote! {
151 impl #impl_generics ::dir_structure::ReadFrom for #name #ty_generics #where_clause {
152 fn read_from(#path_param_name: &std::path::Path) -> ::dir_structure::Result<Self>
153 where
154 Self: Sized,
155 {
156 Ok(Self {
157 #(#field_read_impls,)*
158 })
159 }
160 }
161 impl #impl_generics ::dir_structure::WriteTo for #name #ty_generics #where_clause {
162 fn write_to(&self, #path_param_name: &std::path::Path) -> ::dir_structure::Result<()> {
163 #(#field_write_impls)*
164 Ok(())
165 }
166 }
167 impl #impl_generics ::dir_structure::DirStructure for #name #ty_generics #where_clause {}
168 };
169
170 Ok(expanded)
171}