~repos /rust-embed
GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone
https://git.pyrossh.dev/rust-embed/.git
rust-embed
Discussions:
https://groups.google.com/g/rust-embed-devs
rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.
8e80155b
—
Tommy Yu 1 month ago
Make Way to avoid compile failure on undefined env
- impl/src/lib.rs +87 -57
- tests/interpolated_path.rs +20 -0
impl/src/lib.rs
CHANGED
|
@@ -17,7 +17,7 @@ use std::{
|
|
|
17
17
|
use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue};
|
|
18
18
|
|
|
19
19
|
fn embedded(
|
|
20
|
-
ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String],
|
|
20
|
+
ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: Option<String>, prefix: Option<&str>, includes: &[String], excludes: &[String],
|
|
21
21
|
metadata_only: bool, crate_path: &syn::Path,
|
|
22
22
|
) -> syn::Result<TokenStream2> {
|
|
23
23
|
extern crate rust_embed_utils;
|
|
@@ -28,7 +28,11 @@ fn embedded(
|
|
|
28
28
|
let includes: Vec<&str> = includes.iter().map(AsRef::as_ref).collect();
|
|
29
29
|
let excludes: Vec<&str> = excludes.iter().map(AsRef::as_ref).collect();
|
|
30
30
|
let matcher = PathMatcher::new(&includes, &excludes);
|
|
31
|
+
let entries = absolute_folder_path.clone()
|
|
32
|
+
.map(|v| rust_embed_utils::get_files(v, matcher))
|
|
33
|
+
.into_iter()
|
|
34
|
+
.flatten();
|
|
31
|
-
for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in
|
|
35
|
+
for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in entries {
|
|
32
36
|
match_values.insert(
|
|
33
37
|
rel_path.clone(),
|
|
34
38
|
embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path, metadata_only, crate_path)?,
|
|
@@ -111,7 +115,7 @@ fn embedded(
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
fn dynamic(
|
|
114
|
-
ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool, crate_path: &syn::Path,
|
|
118
|
+
ident: &syn::Ident, folder_path: Option<String>, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool, crate_path: &syn::Path,
|
|
115
119
|
) -> TokenStream2 {
|
|
116
120
|
let (handle_prefix, map_iter) = if let ::std::option::Option::Some(prefix) = prefix {
|
|
117
121
|
(
|
|
@@ -136,27 +140,17 @@ fn dynamic(
|
|
|
136
140
|
.map(|mut file| { file.data = ::std::default::Default::default(); file })
|
|
137
141
|
});
|
|
138
142
|
|
|
143
|
+
let methods = if let Some(ref folder_path) = folder_path {
|
|
139
|
-
|
|
144
|
+
let non_canonical_folder_path = Path::new(&folder_path);
|
|
140
|
-
|
|
145
|
+
let canonical_folder_path = non_canonical_folder_path
|
|
141
|
-
|
|
146
|
+
.canonicalize()
|
|
142
|
-
|
|
147
|
+
.or_else(|err| match err {
|
|
143
|
-
|
|
148
|
+
err if err.kind() == ErrorKind::NotFound => Ok(non_canonical_folder_path.to_owned()),
|
|
144
|
-
|
|
149
|
+
err => Err(err),
|
|
145
|
-
|
|
150
|
+
})
|
|
146
|
-
|
|
151
|
+
.expect("folder path must resolve to an absolute path");
|
|
147
|
-
|
|
152
|
+
let canonical_folder_path = canonical_folder_path.to_str().expect("absolute folder path must be valid unicode");
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
quote! {
|
|
150
|
-
#[cfg(debug_assertions)]
|
|
151
|
-
impl #ident {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
fn matcher() -> #crate_path::utils::PathMatcher {
|
|
155
|
-
#declare_includes
|
|
156
|
-
#declare_excludes
|
|
157
|
-
static PATH_MATCHER: ::std::sync::OnceLock<#crate_path::utils::PathMatcher> = ::std::sync::OnceLock::new();
|
|
158
|
-
PATH_MATCHER.get_or_init(|| #crate_path::utils::PathMatcher::new(INCLUDES, EXCLUDES)).clone()
|
|
159
|
-
}
|
|
160
154
|
/// Get an embedded file and its metadata.
|
|
161
155
|
pub fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> {
|
|
162
156
|
#handle_prefix
|
|
@@ -191,10 +185,38 @@ fn dynamic(
|
|
|
191
185
|
pub fn iter() -> impl ::std::iter::Iterator<Item = ::std::borrow::Cow<'static, str>> + 'static {
|
|
192
186
|
use ::std::path::Path;
|
|
193
187
|
|
|
194
|
-
|
|
195
188
|
#crate_path::utils::get_files(::std::string::String::from(#folder_path), Self::matcher())
|
|
196
189
|
.map(|e| #map_iter)
|
|
197
190
|
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
quote! {
|
|
194
|
+
/// Get an embedded file and its metadata.
|
|
195
|
+
pub fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> {
|
|
196
|
+
// No folder_path defined so nothing can be returned.
|
|
197
|
+
None
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Iterates over the file paths in the folder.
|
|
201
|
+
pub fn iter() -> impl ::std::iter::Iterator<Item = ::std::borrow::Cow<'static, str>> + 'static {
|
|
202
|
+
None.into_iter()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
quote! {
|
|
208
|
+
#[cfg(debug_assertions)]
|
|
209
|
+
impl #ident {
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
fn matcher() -> #crate_path::utils::PathMatcher {
|
|
213
|
+
#declare_includes
|
|
214
|
+
#declare_excludes
|
|
215
|
+
static PATH_MATCHER: ::std::sync::OnceLock<#crate_path::utils::PathMatcher> = ::std::sync::OnceLock::new();
|
|
216
|
+
PATH_MATCHER.get_or_init(|| #crate_path::utils::PathMatcher::new(INCLUDES, EXCLUDES)).clone()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#methods
|
|
198
220
|
}
|
|
199
221
|
|
|
200
222
|
#[cfg(debug_assertions)]
|
|
@@ -211,7 +233,7 @@ fn dynamic(
|
|
|
211
233
|
}
|
|
212
234
|
|
|
213
235
|
fn generate_assets(
|
|
214
|
-
ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<String>, includes: Vec<String>, excludes: Vec<String>,
|
|
236
|
+
ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: Option<String>, prefix: Option<String>, includes: Vec<String>, excludes: Vec<String>,
|
|
215
237
|
metadata_only: bool, crate_path: &syn::Path,
|
|
216
238
|
) -> syn::Result<TokenStream2> {
|
|
217
239
|
let embedded_impl = embedded(
|
|
@@ -346,7 +368,7 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
|
|
|
346
368
|
"#[derive(RustEmbed)] must contain one attribute like this #[folder = \"examples/public/\"]",
|
|
347
369
|
));
|
|
348
370
|
}
|
|
349
|
-
let folder_path = folder_paths.remove(0);
|
|
371
|
+
let folder_path = Some(folder_paths.remove(0));
|
|
350
372
|
|
|
351
373
|
let prefix = find_attribute_values(ast, "prefix").into_iter().next();
|
|
352
374
|
let includes = find_attribute_values(ast, "include");
|
|
@@ -362,41 +384,49 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
|
|
|
362
384
|
));
|
|
363
385
|
}
|
|
364
386
|
|
|
387
|
+
// .map_err(|v| syn::Error::new_spanned(ast, v.to_string()))?
|
|
365
388
|
#[cfg(feature = "interpolate-folder-path")]
|
|
366
|
-
let folder_path = shellexpand::full(&folder_path)
|
|
389
|
+
let folder_path = match shellexpand::full(&folder_path.unwrap()) {
|
|
390
|
+
Ok(v) => Ok(Some(v.to_string())),
|
|
391
|
+
Err(v) if v.cause == std::env::VarError::NotPresent && allow_missing => Ok(None),
|
|
367
|
-
|
|
392
|
+
Err(v) => Err(syn::Error::new_spanned(ast, v.to_string())),
|
|
368
|
-
|
|
393
|
+
}?;
|
|
369
394
|
|
|
370
395
|
// Base relative paths on the Cargo.toml location
|
|
396
|
+
let (relative_path, absolute_folder_path) = if let Some(folder_path) = folder_path {
|
|
371
|
-
|
|
397
|
+
let (relative_path, absolute_folder_path) = if Path::new(&folder_path).is_relative() {
|
|
372
|
-
|
|
398
|
+
let absolute_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
|
|
373
|
-
|
|
399
|
+
.join(&folder_path)
|
|
374
|
-
|
|
400
|
+
.to_str()
|
|
375
|
-
|
|
401
|
+
.unwrap()
|
|
376
|
-
|
|
402
|
+
.to_owned();
|
|
377
|
-
|
|
403
|
+
(Some(folder_path.clone()), absolute_path)
|
|
378
|
-
|
|
404
|
+
} else {
|
|
379
|
-
|
|
405
|
+
if cfg!(feature = "compression") {
|
|
380
|
-
|
|
406
|
+
return Err(syn::Error::new_spanned(ast, "`folder` must be a relative path under `compression` feature."));
|
|
381
|
-
|
|
407
|
+
}
|
|
382
|
-
|
|
408
|
+
(None, folder_path)
|
|
383
|
-
|
|
409
|
+
};
|
|
384
|
-
|
|
385
|
-
if !Path::new(&absolute_folder_path).exists() && !allow_missing {
|
|
386
|
-
let mut message = format!(
|
|
387
|
-
"#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
|
|
388
|
-
absolute_folder_path,
|
|
389
|
-
std::env::current_dir().unwrap().to_str().unwrap()
|
|
390
|
-
);
|
|
391
410
|
|
|
411
|
+
if !Path::new(&absolute_folder_path).exists() && !allow_missing {
|
|
412
|
+
let mut message = format!(
|
|
413
|
+
"#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
|
|
414
|
+
absolute_folder_path,
|
|
415
|
+
std::env::current_dir().unwrap().to_str().unwrap()
|
|
416
|
+
);
|
|
417
|
+
|
|
392
|
-
|
|
418
|
+
// Add a message about the interpolate-folder-path feature if the path may
|
|
393
|
-
|
|
419
|
+
// include a variable
|
|
394
|
-
|
|
420
|
+
if absolute_folder_path.contains('$') && cfg!(not(feature = "interpolate-folder-path")) {
|
|
395
|
-
|
|
421
|
+
message += "\nA variable has been detected. RustEmbed can expand variables \
|
|
396
|
-
|
|
422
|
+
when the `interpolate-folder-path` feature is enabled.";
|
|
397
|
-
|
|
423
|
+
}
|
|
398
424
|
|
|
399
|
-
|
|
425
|
+
return Err(syn::Error::new_spanned(ast, message));
|
|
426
|
+
};
|
|
427
|
+
(relative_path, Some(absolute_folder_path))
|
|
428
|
+
} else {
|
|
429
|
+
(None, None)
|
|
400
430
|
};
|
|
401
431
|
|
|
402
432
|
generate_assets(
|
tests/interpolated_path.rs
CHANGED
|
@@ -5,6 +5,26 @@ use rust_embed::Embed;
|
|
|
5
5
|
#[folder = "$CARGO_MANIFEST_DIR/examples/public/"]
|
|
6
6
|
struct Asset;
|
|
7
7
|
|
|
8
|
+
static CHECK_UNDEFINED: Option<&str> = option_env!("__UNDEFINED__");
|
|
9
|
+
|
|
10
|
+
#[derive(Embed)]
|
|
11
|
+
#[folder = "$__UNDEFINED__/examples/public/"]
|
|
12
|
+
#[allow_missing = true]
|
|
13
|
+
struct UndefinedAsset;
|
|
14
|
+
|
|
15
|
+
#[test]
|
|
16
|
+
fn undefined_usable() {
|
|
17
|
+
assert!(CHECK_UNDEFINED.is_none(), "__UNDEFINED__ should not be defined at compile time");
|
|
18
|
+
assert!(UndefinedAsset::get("index.html").is_none(), "index.html should not exist");
|
|
19
|
+
|
|
20
|
+
let mut num_files = 0;
|
|
21
|
+
for file in UndefinedAsset::iter() {
|
|
22
|
+
assert!(UndefinedAsset::get(file.as_ref()).is_some());
|
|
23
|
+
num_files += 1;
|
|
24
|
+
}
|
|
25
|
+
assert_eq!(num_files, 0);
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
#[test]
|
|
9
29
|
fn get_works() {
|
|
10
30
|
assert!(Asset::get("index.html").is_some(), "index.html should exist");
|