rust_embed_for_web_impl/
lib.rs

1//! This crate contains the implementation of the `RustEmbed` macro for
2//! `rust-embed-for-web`.
3//!
4//! You generally don't want to use this crate directly, `rust-embed-for-web`
5//! re-exports any necessary parts from this crate.
6#![recursion_limit = "1024"]
7#![forbid(unsafe_code)]
8#[macro_use]
9extern crate quote;
10extern crate proc_macro;
11
12mod attributes;
13mod compress;
14mod dynamic;
15mod embed;
16
17use attributes::read_attribute_config;
18use dynamic::generate_dynamic_impl;
19use embed::generate_embed_impl;
20use proc_macro::TokenStream;
21use proc_macro2::TokenStream as TokenStream2;
22use std::{env, path::Path};
23use syn::{Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue};
24
25/// Find all pairs of the `name = "value"` attribute from the derive input
26fn find_attribute_values(ast: &syn::DeriveInput, attr_name: &str) -> Vec<String> {
27    ast.attrs
28        .iter()
29        .filter(|value| value.path().is_ident(attr_name))
30        .map(|attr| &attr.meta)
31        .filter_map(|meta| match meta {
32            Meta::NameValue(MetaNameValue {
33                value:
34                    Expr::Lit(ExprLit {
35                        lit: Lit::Str(val), ..
36                    }),
37                ..
38            }) => Some(val.value()),
39            _ => None,
40        })
41        .collect()
42}
43
44fn impl_rust_embed_for_web(ast: &syn::DeriveInput) -> TokenStream2 {
45    match ast.data {
46        Data::Struct(ref data) => match data.fields {
47            Fields::Unit => {}
48            _ => panic!("RustEmbed can only be derived for unit structs"),
49        },
50        _ => panic!("RustEmbed can only be derived for unit structs"),
51    };
52
53    let mut folder_paths = find_attribute_values(ast, "folder");
54    if folder_paths.len() != 1 {
55        panic!("#[derive(RustEmbed)] must contain one and only one folder attribute");
56    }
57    let folder_path = folder_paths.remove(0);
58    #[cfg(feature = "interpolate-folder-path")]
59    let folder_path = shellexpand::full(&folder_path).unwrap().to_string();
60
61    // Base relative paths on the Cargo.toml location
62    let folder_path = if Path::new(&folder_path).is_relative() {
63        Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
64            .join(folder_path)
65            .to_str()
66            .unwrap()
67            .to_owned()
68    } else {
69        folder_path
70    };
71
72    let config = read_attribute_config(ast);
73
74    let prefixes = find_attribute_values(ast, "prefix");
75    let prefix = if prefixes.is_empty() {
76        "".to_string()
77    } else if prefixes.len() == 1 {
78        prefixes[0].clone()
79    } else {
80        panic!("#[derive(RustEmbed)] must have at most one prefix, you supplied several");
81    };
82
83    if cfg!(debug_assertions) && !cfg!(feature = "always-embed") {
84        generate_dynamic_impl(&ast.ident, &config, &folder_path, &prefix)
85    } else {
86        generate_embed_impl(&ast.ident, &config, &folder_path, &prefix)
87    }
88}
89
90#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude, gzip, br))]
91/// A folder that is embedded into your program.
92///
93/// For example:
94///
95/// ```ignore
96/// #[derive(RustEmbed)]
97/// #[folder = "examples/public"]
98/// struct MyEmbeddedFiles;
99/// ```
100///
101/// The `folder` is relative to where your `Cargo.toml` file is located. This
102/// example will embed the files under `<your-workspace>/examples/public` into
103/// your program.
104///
105/// Please check the package readme for more details.
106pub fn derive_input_object(input: TokenStream) -> TokenStream {
107    let ast: DeriveInput = syn::parse(input).unwrap();
108    let gen = impl_rust_embed_for_web(&ast);
109    gen.into()
110}