~repos /rust-embed
git clone https://pyrossh.dev/repos/rust-embed.git
rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.
71637d93
—
Mcat12 4 years ago
Support a prefix annotation
- impl/src/lib.rs +53 -30
- tests/prefix.rs +24 -0
impl/src/lib.rs
CHANGED
|
@@ -6,9 +6,9 @@ extern crate proc_macro;
|
|
|
6
6
|
use proc_macro::TokenStream;
|
|
7
7
|
use proc_macro2::TokenStream as TokenStream2;
|
|
8
8
|
use std::{env, path::Path};
|
|
9
|
-
use syn::{Data, DeriveInput, Fields, Lit, Meta};
|
|
9
|
+
use syn::{Data, DeriveInput, Fields, Lit, Meta, MetaNameValue};
|
|
10
10
|
|
|
11
|
-
fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
11
|
+
fn embedded(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> TokenStream2 {
|
|
12
12
|
extern crate rust_embed_utils;
|
|
13
13
|
|
|
14
14
|
let mut match_values = Vec::<TokenStream2>::new();
|
|
@@ -16,7 +16,12 @@ fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
|
16
16
|
|
|
17
17
|
for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(folder_path) {
|
|
18
18
|
match_values.push(embed_file(&rel_path, &full_canonical_path));
|
|
19
|
+
|
|
19
|
-
list_values.push(
|
|
20
|
+
list_values.push(if let Some(prefix) = prefix {
|
|
21
|
+
format!("{}{}", prefix, rel_path)
|
|
22
|
+
} else {
|
|
23
|
+
rel_path
|
|
24
|
+
});
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
let array_len = list_values.len();
|
|
@@ -29,10 +34,19 @@ fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
|
29
34
|
quote! { #[cfg(not(debug_assertions))]}
|
|
30
35
|
};
|
|
31
36
|
|
|
37
|
+
let handle_prefix = if let Some(prefix) = prefix {
|
|
38
|
+
quote! {
|
|
39
|
+
let file_path = file_path.strip_prefix(#prefix)?;
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
TokenStream2::new()
|
|
43
|
+
};
|
|
44
|
+
|
|
32
45
|
quote! {
|
|
33
46
|
#not_debug_attr
|
|
34
47
|
impl #ident {
|
|
35
48
|
pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
|
|
49
|
+
#handle_prefix
|
|
36
50
|
match file_path.replace("\\", "/").as_str() {
|
|
37
51
|
#(#match_values)*
|
|
38
52
|
_ => None,
|
|
@@ -61,7 +75,16 @@ fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
|
|
64
|
-
fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
78
|
+
fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> TokenStream2 {
|
|
79
|
+
let (handle_prefix, map_iter) = if let Some(prefix) = prefix {
|
|
80
|
+
(
|
|
81
|
+
quote! { let file_path = file_path.strip_prefix(#prefix)?; },
|
|
82
|
+
quote! { std::borrow::Cow::Owned(format!("{}{}", #prefix, e.rel_path)) },
|
|
83
|
+
)
|
|
84
|
+
} else {
|
|
85
|
+
(TokenStream2::new(), quote! { std::borrow::Cow::from(e.rel_path) })
|
|
86
|
+
};
|
|
87
|
+
|
|
65
88
|
quote! {
|
|
66
89
|
#[cfg(debug_assertions)]
|
|
67
90
|
impl #ident {
|
|
@@ -69,6 +92,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
|
69
92
|
use std::fs;
|
|
70
93
|
use std::path::Path;
|
|
71
94
|
|
|
95
|
+
#handle_prefix
|
|
96
|
+
|
|
72
97
|
let file_path = Path::new(#folder_path).join(file_path.replace("\\", "/"));
|
|
73
98
|
match fs::read(file_path) {
|
|
74
99
|
Ok(contents) => Some(std::borrow::Cow::from(contents)),
|
|
@@ -80,7 +105,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
|
80
105
|
|
|
81
106
|
pub fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
|
|
82
107
|
use std::path::Path;
|
|
83
|
-
rust_embed::utils::get_files(String::from(#folder_path))
|
|
108
|
+
rust_embed::utils::get_files(String::from(#folder_path))
|
|
109
|
+
.map(|e| #map_iter)
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
112
|
|
|
@@ -97,13 +123,13 @@ fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
|
97
123
|
}
|
|
98
124
|
}
|
|
99
125
|
|
|
100
|
-
fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
126
|
+
fn generate_assets(ident: &syn::Ident, folder_path: String, prefix: Option<String>) -> TokenStream2 {
|
|
101
|
-
let embedded_impl = embedded(ident, folder_path.clone());
|
|
127
|
+
let embedded_impl = embedded(ident, folder_path.clone(), prefix.as_deref());
|
|
102
128
|
if cfg!(feature = "debug-embed") {
|
|
103
129
|
return embedded_impl;
|
|
104
130
|
}
|
|
105
131
|
|
|
106
|
-
let dynamic_impl = dynamic(ident, folder_path);
|
|
132
|
+
let dynamic_impl = dynamic(ident, folder_path, prefix.as_deref());
|
|
107
133
|
|
|
108
134
|
quote! {
|
|
109
135
|
#embedded_impl
|
|
@@ -112,9 +138,9 @@ fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
|
|
|
112
138
|
}
|
|
113
139
|
|
|
114
140
|
#[cfg(not(feature = "compression"))]
|
|
115
|
-
fn embed_file(
|
|
141
|
+
fn embed_file(match_str: &str, full_canonical_path: &str) -> TokenStream2 {
|
|
116
142
|
quote! {
|
|
117
|
-
#
|
|
143
|
+
#match_str => {
|
|
118
144
|
let bytes = &include_bytes!(#full_canonical_path)[..];
|
|
119
145
|
Some(std::borrow::Cow::from(bytes))
|
|
120
146
|
},
|
|
@@ -133,6 +159,19 @@ fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
|
|
|
133
159
|
}
|
|
134
160
|
}
|
|
135
161
|
|
|
162
|
+
/// Find a `name = "value"` attribute from the derive input
|
|
163
|
+
fn find_attribute_value(ast: &syn::DeriveInput, attr_name: &str) -> Option<String> {
|
|
164
|
+
ast
|
|
165
|
+
.attrs
|
|
166
|
+
.iter()
|
|
167
|
+
.find(|value| value.path.is_ident(attr_name))
|
|
168
|
+
.and_then(|attr| attr.parse_meta().ok())
|
|
169
|
+
.and_then(|meta| match meta {
|
|
170
|
+
Meta::NameValue(MetaNameValue { lit: Lit::Str(val), .. }) => Some(val.value()),
|
|
171
|
+
_ => None,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
136
175
|
fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
|
|
137
176
|
match ast.data {
|
|
138
177
|
Data::Struct(ref data) => match data.fields {
|
|
@@ -142,24 +181,8 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
|
|
|
142
181
|
_ => panic!("RustEmbed can only be derived for unit structs"),
|
|
143
182
|
};
|
|
144
183
|
|
|
145
|
-
let attribute = ast
|
|
146
|
-
.attrs
|
|
147
|
-
.iter()
|
|
148
|
-
.find(|value| value.path.is_ident("folder"))
|
|
149
|
-
.expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
|
|
150
|
-
let meta = attribute
|
|
151
|
-
.parse_meta()
|
|
152
|
-
.expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
|
|
153
|
-
let literal_value = match meta {
|
|
154
|
-
Meta::NameValue(ref data) => &data.lit,
|
|
155
|
-
|
|
184
|
+
let folder_path = find_attribute_value(ast, "folder").expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
|
|
156
|
-
};
|
|
157
|
-
let folder_path = match literal_value {
|
|
158
|
-
Lit::Str(ref val) => val.clone().value(),
|
|
159
|
-
_ => {
|
|
160
|
-
|
|
185
|
+
let prefix = find_attribute_value(ast, "prefix");
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
186
|
|
|
164
187
|
#[cfg(feature = "interpolate-folder-path")]
|
|
165
188
|
let folder_path = shellexpand::full(&folder_path).unwrap().to_string();
|
|
@@ -192,10 +215,10 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
|
|
|
192
215
|
panic!(message);
|
|
193
216
|
};
|
|
194
217
|
|
|
195
|
-
generate_assets(&ast.ident, folder_path)
|
|
218
|
+
generate_assets(&ast.ident, folder_path, prefix)
|
|
196
219
|
}
|
|
197
220
|
|
|
198
|
-
#[proc_macro_derive(RustEmbed, attributes(folder))]
|
|
221
|
+
#[proc_macro_derive(RustEmbed, attributes(folder, prefix))]
|
|
199
222
|
pub fn derive_input_object(input: TokenStream) -> TokenStream {
|
|
200
223
|
let ast: DeriveInput = syn::parse(input).unwrap();
|
|
201
224
|
let gen = impl_rust_embed(&ast);
|
tests/prefix.rs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
use rust_embed::RustEmbed;
|
|
2
|
+
|
|
3
|
+
#[derive(RustEmbed)]
|
|
4
|
+
#[folder = "examples/public/"]
|
|
5
|
+
#[prefix = "prefix/"]
|
|
6
|
+
struct Asset;
|
|
7
|
+
|
|
8
|
+
#[test]
|
|
9
|
+
fn get_with_prefix() {
|
|
10
|
+
assert!(Asset::get("prefix/index.html").is_some());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#[test]
|
|
14
|
+
fn get_without_prefix() {
|
|
15
|
+
assert!(Asset::get("index.html").is_none());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[test]
|
|
19
|
+
fn iter_values_have_prefix() {
|
|
20
|
+
for file in Asset::iter() {
|
|
21
|
+
assert!(file.starts_with("prefix/"));
|
|
22
|
+
assert!(Asset::get(file.as_ref()).is_some());
|
|
23
|
+
}
|
|
24
|
+
}
|