1use proc_macro::TokenStream;
2use syn::{Data, DeriveInput, Fields, parse_macro_input};
3
4#[proc_macro_derive(FromDir)]
5pub fn from_dir(input: TokenStream) -> TokenStream {
6 let input = parse_macro_input!(input as DeriveInput);
7
8 let ident = input.ident;
9 let fields = match input.data {
10 Data::Struct(data) => match data.fields {
11 Fields::Named(fields) => fields,
12 _ => panic!("FromDir only supports named fields"),
13 },
14 _ => panic!("FromDir only supports structs"),
15 };
16
17 let field_idents = fields
18 .named
19 .iter()
20 .map(|f| {
21 f.ident
22 .as_ref()
23 .expect("Named field should have an identifier")
24 })
25 .collect::<Vec<_>>();
26
27 let field_types = fields.named.iter().map(|f| &f.ty).collect::<Vec<_>>();
28
29 let expanded = quote::quote! {
30 #[automatically_derived]
31 impl #ident {
32 pub fn from_dir(dir: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
33 use std::fs;
34 use std::io::Read;
35 use std::str::FromStr;
36 use std::any::TypeId;
37
38 let dir = dir.as_ref();
39 let string_type_id = TypeId::of::<String>();
40
41 #(
42 let mut #field_idents: #field_types = {
43 let mut data = fs::read_to_string(dir.join(stringify!(#field_idents)))?;
44 let data = if TypeId::of::<#field_types>() == string_type_id {
45 &data
46 } else {
47 data.trim()
48 };
49 #field_types::from_str(data).expect("TODO")
50 };
51 )*
52
53 Ok(Self {
54 #(#field_idents),*
55 })
56 }
57 }
58 };
59
60 TokenStream::from(expanded)
61}