1use proc_macro::TokenStream;
2use quote::quote;
3use std::{fs::canonicalize, path::Path};
4use syn::{parse_macro_input, DeriveInput, Expr, ExprLit, Ident, Lit, Meta, MetaNameValue};
5use walkdir::WalkDir;
6
7#[proc_macro_derive(Embedded, attributes(folder))]
9pub fn derive_embeddy(input: TokenStream) -> TokenStream {
10 let input: DeriveInput = parse_macro_input!(input);
11 let ident: Ident = input.ident;
12
13 let folder: String = input
15 .attrs
16 .iter()
17 .find(|attr| attr.path().is_ident("folder"))
18 .and_then(|attr| match &attr.meta {
19 Meta::NameValue(MetaNameValue {
20 value:
21 Expr::Lit(ExprLit {
22 lit: Lit::Str(val), ..
23 }),
24 ..
25 }) => Some(val.value()),
26 _ => None,
27 })
28 .expect("Missing folder attribute");
29
30 let folder_path = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(folder);
32
33 if !folder_path.exists() {
34 panic!(
35 "Unable to embed folder that doesn't exist: {}",
36 folder_path.display()
37 )
38 }
39
40 let mut includes = Vec::new();
41
42 let files = WalkDir::new(&folder_path)
44 .into_iter()
45 .filter_map(|entry| entry.ok())
46 .filter(|entry| entry.file_type().is_file());
47
48 for file in files {
49 let rel_path = file
50 .path()
51 .strip_prefix(&folder_path)
53 .unwrap()
54 .to_str()
55 .expect("Path missing str repr")
56 .replace("\\", "/");
58
59 let full_path = canonicalize(file.path()).expect("Failed to get file canonical path");
60 let full_path = full_path.to_str().expect("Path missing str repr");
61
62 includes.push(quote! {
63 #rel_path => include_bytes!(#full_path),
64 });
65 }
66
67 quote! {
68 impl embeddy::Embedded for #ident {
69
70 fn get(path: &str) -> Option<&'static [u8]> {
71 Some(match path {
72 #(#includes)*
73 _ => return None
74 })
75 }
76 }
77 }
78 .into()
79}