1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{Lit, LitStr, parse::Parse, parse_macro_input};
5
6
7
8#[proc_macro]
12pub fn fs_embed(input: TokenStream) -> TokenStream {
13 let args = parse_macro_input!(input as EmbedArgs);
14
15 let rel_lit: LitStr = match args.path {
16 Lit::Str(s) => s,
17 other => return compile_error("first argument must be a string literal", other.span()),
18 };
19
20 let rel_path = rel_lit.value();
21 let call_span = rel_lit.span(); let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") {
25 Ok(dir) => dir,
26 Err(_) => return compile_error("fs_embed!: CARGO_MANIFEST_DIR not set", call_span),
27 };
28
29 let full_path = match std::path::Path::new(&manifest_dir)
30 .join(&rel_path)
31 .canonicalize()
32 .map_err(|_| {
33 syn::Error::new(
34 call_span,
35 format!("fs_embed!: failed to resolve path: {}", rel_path),
36 )
37 }) {
38 Ok(p) => p,
39 Err(msg) => return compile_error(msg.to_string(), call_span),
40 };
41
42 let full_path = match full_path.to_str() {
43 Some(p) => p,
44 None => return compile_error("fs_embed!: path must be valid UTF-8", call_span),
45 };
46
47 if !full_path.starts_with(&manifest_dir) {
48 let msg = format!(
49 "fs_embed!: directory not found:\n {full_path}\n expected to be inside crate root:\n {manifest_dir}\n relative path: {rel_path}",
50 );
51 return compile_error(&msg, call_span);
52 };
53
54 let full_literal: LitStr = LitStr::new(full_path, call_span);
55
56 let embed_code = quote! {
57 ::fs_embed::Dir::from_embedded(include_dir::include_dir!(#full_literal), #full_literal)
58 };
59
60 quote! { #embed_code }.into()
61}
62
63
64
65#[doc(hidden)]
67fn compile_error<S: AsRef<str>>(msg: S, span: Span) -> TokenStream {
68 let lit = LitStr::new(msg.as_ref(), span);
69 quote!(compile_error!(#lit)).into()
70}
71
72struct EmbedArgs {
73 path: Lit,
74}
75
76impl Parse for EmbedArgs {
77 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
78 let path: Lit = input.parse()?;
79 Ok(EmbedArgs { path })
80 }
81}