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