~repos /rust-embed

#rust#proc-macro#http

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.


Files changed (2) hide show
  1. impl/src/lib.rs +87 -57
  2. 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 rust_embed_utils::get_files(absolute_folder_path.clone(), matcher) {
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
- let non_canonical_folder_path = Path::new(&folder_path);
144
+ let non_canonical_folder_path = Path::new(&folder_path);
140
- let canonical_folder_path = non_canonical_folder_path
145
+ let canonical_folder_path = non_canonical_folder_path
141
- .canonicalize()
146
+ .canonicalize()
142
- .or_else(|err| match err {
147
+ .or_else(|err| match err {
143
- err if err.kind() == ErrorKind::NotFound => Ok(non_canonical_folder_path.to_owned()),
148
+ err if err.kind() == ErrorKind::NotFound => Ok(non_canonical_folder_path.to_owned()),
144
- err => Err(err),
149
+ err => Err(err),
145
- })
150
+ })
146
- .expect("folder path must resolve to an absolute path");
151
+ .expect("folder path must resolve to an absolute path");
147
- let canonical_folder_path = canonical_folder_path.to_str().expect("absolute folder path must be valid unicode");
152
+ let canonical_folder_path = canonical_folder_path.to_str().expect("absolute folder path must be valid unicode");
148
-
149
- quote! {
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
- .map_err(|v| syn::Error::new_spanned(ast, v.to_string()))?
392
+ Err(v) => Err(syn::Error::new_spanned(ast, v.to_string())),
368
- .to_string();
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
- let (relative_path, absolute_folder_path) = if Path::new(&folder_path).is_relative() {
397
+ let (relative_path, absolute_folder_path) = if Path::new(&folder_path).is_relative() {
372
- let absolute_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
398
+ let absolute_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
373
- .join(&folder_path)
399
+ .join(&folder_path)
374
- .to_str()
400
+ .to_str()
375
- .unwrap()
401
+ .unwrap()
376
- .to_owned();
402
+ .to_owned();
377
- (Some(folder_path.clone()), absolute_path)
403
+ (Some(folder_path.clone()), absolute_path)
378
- } else {
404
+ } else {
379
- if cfg!(feature = "compression") {
405
+ if cfg!(feature = "compression") {
380
- return Err(syn::Error::new_spanned(ast, "`folder` must be a relative path under `compression` feature."));
406
+ return Err(syn::Error::new_spanned(ast, "`folder` must be a relative path under `compression` feature."));
381
- }
407
+ }
382
- (None, folder_path)
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
- // Add a message about the interpolate-folder-path feature if the path may
418
+ // Add a message about the interpolate-folder-path feature if the path may
393
- // include a variable
419
+ // include a variable
394
- if absolute_folder_path.contains('$') && cfg!(not(feature = "interpolate-folder-path")) {
420
+ if absolute_folder_path.contains('$') && cfg!(not(feature = "interpolate-folder-path")) {
395
- message += "\nA variable has been detected. RustEmbed can expand variables \
421
+ message += "\nA variable has been detected. RustEmbed can expand variables \
396
- when the `interpolate-folder-path` feature is enabled.";
422
+ when the `interpolate-folder-path` feature is enabled.";
397
- }
423
+ }
398
424
 
399
- return Err(syn::Error::new_spanned(ast, message));
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");