axum_embed_files_macros/
lib.rs

1use std::path::Path;
2
3use proc_macro::TokenStream;
4use quote::quote;
5
6use axum_embed_files_core::{content_type, etag, last_modified};
7
8#[derive(Debug)]
9struct Input {
10    filename: String,
11    dir: Option<String>,
12}
13
14impl syn::parse::Parse for Input {
15    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
16        let first_str: syn::LitStr = input.parse()?;
17        if input.is_empty() {
18            return Ok(Input {
19                filename: first_str.value(),
20                dir: None,
21            });
22        }
23
24        let _: syn::Token![,] = input.parse()?;
25
26        let filename: syn::LitStr = input.parse()?;
27
28        Ok(Input {
29            filename: filename.value(),
30            dir: Some(first_str.value()),
31        })
32    }
33}
34
35#[cfg(not(feature = "serve-from-fs"))]
36fn render_bytes(file_path: &Path) -> proc_macro2::TokenStream {
37    let file_path_str = file_path.display().to_string(); // TODO: non-unicode
38    quote!(include_bytes!(#file_path_str))
39}
40
41#[cfg(feature = "serve-from-fs")]
42fn render_bytes(_file_path: &Path) -> proc_macro2::TokenStream {
43    let empty = syn::LitByteStr::new(&[], proc_macro2::Span::call_site());
44    quote!(#empty)
45}
46
47/// Embed a local file in a tower Service.
48///
49/// ```nobuild
50/// embed_file!(path: &str) -> EmbeddedFileService;
51/// embed_file!(dir: &str, path: &str) -> EmbeddedFileService;
52/// ```
53///
54/// This macro loads the specified file from the file system and embeds it
55/// within an [EmbeddedFileService](service/struct.EmbeddedFileService.html).
56/// The path is relative to your crate's `CARGO_MANIFEST_DIR`.  The modified
57/// timestamp is taken from the file's metadata.
58#[proc_macro]
59pub fn embed_file(params: TokenStream) -> TokenStream {
60    let input: Input = syn::parse(params).unwrap();
61
62    let file_dir = {
63        let mut p = std::env::current_dir().unwrap();
64        if let Some(next) = &input.dir {
65            p = p.join(next);
66        }
67        p
68    };
69
70    let file_path = file_dir.join(&input.filename);
71
72    let contents = std::fs::read(&file_path).unwrap();
73
74    let directory = file_dir.display().to_string();
75    let filename = &input.filename;
76    let bytes = render_bytes(&file_path);
77    let etag = etag::generate(&contents[..]);
78    let last_modified = last_modified::of_path(&file_path).unwrap();
79    let content_type = match content_type::guess_from_path(&file_path) {
80        None => quote!(None),
81        Some(t) => quote!(Some(#t)),
82    };
83
84    quote! {
85        ::axum_embed_files::service::EmbeddedFileService {
86            directory: #directory,
87            filename: #filename,
88            bytes: #bytes,
89            etag: #etag,
90            last_modified: #last_modified,
91            content_type: #content_type,
92        }
93    }.into()
94}