~repos /rust-embed

#rust#proc-macro#http

git clone https://pyrossh.dev/repos/rust-embed.git
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 (3) hide show
  1. impl/src/lib.rs +47 -8
  2. readme.md +7 -1
  3. tests/metadata_only.rs +12 -0
impl/src/lib.rs CHANGED
@@ -16,6 +16,7 @@ use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta
16
16
 
17
17
  fn embedded(
18
18
  ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String],
19
+ metadata_only: bool,
19
20
  ) -> syn::Result<TokenStream2> {
20
21
  extern crate rust_embed_utils;
21
22
 
@@ -25,7 +26,10 @@ fn embedded(
25
26
  let includes: Vec<&str> = includes.iter().map(AsRef::as_ref).collect();
26
27
  let excludes: Vec<&str> = excludes.iter().map(AsRef::as_ref).collect();
27
28
  for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(absolute_folder_path.clone(), &includes, &excludes) {
29
+ match_values.insert(
30
+ rel_path.clone(),
28
- match_values.insert(rel_path.clone(), embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path)?);
31
+ embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path, metadata_only)?,
32
+ );
29
33
 
30
34
  list_values.push(if let Some(prefix) = prefix {
31
35
  format!("{}{}", prefix, rel_path)
@@ -103,7 +107,7 @@ fn embedded(
103
107
  })
104
108
  }
105
109
 
106
- fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String]) -> TokenStream2 {
110
+ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool) -> TokenStream2 {
107
111
  let (handle_prefix, map_iter) = if let ::std::option::Option::Some(prefix) = prefix {
108
112
  (
109
113
  quote! { let file_path = file_path.strip_prefix(#prefix)?; },
@@ -121,6 +125,12 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
121
125
  const EXCLUDES: &[&str] = &[#(#excludes),*];
122
126
  };
123
127
 
128
+ // In metadata_only mode, we still need to read file contents to generate the file hash, but
129
+ // then we drop the file data.
130
+ let strip_contents = metadata_only.then_some(quote! {
131
+ .map(|mut file| { file.data = ::std::default::Default::default(); file })
132
+ });
133
+
124
134
  let canonical_folder_path = Path::new(&folder_path).canonicalize().expect("folder path must resolve to an absolute path");
125
135
  let canonical_folder_path = canonical_folder_path.to_str().expect("absolute folder path must be valid unicode");
126
136
 
@@ -154,7 +164,7 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
154
164
  }
155
165
 
156
166
  if rust_embed::utils::is_path_included(&rel_file_path, INCLUDES, EXCLUDES) {
157
- rust_embed::utils::read_file_from_fs(&canonical_file_path).ok()
167
+ rust_embed::utils::read_file_from_fs(&canonical_file_path).ok() #strip_contents
158
168
  } else {
159
169
  ::std::option::Option::None
160
170
  }
@@ -187,6 +197,7 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
187
197
 
188
198
  fn generate_assets(
189
199
  ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<String>, includes: Vec<String>, excludes: Vec<String>,
200
+ metadata_only: bool,
190
201
  ) -> syn::Result<TokenStream2> {
191
202
  let embedded_impl = embedded(
192
203
  ident,
@@ -195,12 +206,13 @@ fn generate_assets(
195
206
  prefix.as_deref(),
196
207
  &includes,
197
208
  &excludes,
209
+ metadata_only,
198
210
  );
199
211
  if cfg!(feature = "debug-embed") {
200
212
  return embedded_impl;
201
213
  }
202
214
  let embedded_impl = embedded_impl?;
203
- let dynamic_impl = dynamic(ident, absolute_folder_path, prefix.as_deref(), &includes, &excludes);
215
+ let dynamic_impl = dynamic(ident, absolute_folder_path, prefix.as_deref(), &includes, &excludes, metadata_only);
204
216
 
205
217
  Ok(quote! {
206
218
  #embedded_impl
@@ -208,7 +220,7 @@ fn generate_assets(
208
220
  })
209
221
  }
210
222
 
211
- fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str) -> syn::Result<TokenStream2> {
223
+ fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str, metadata_only: bool) -> syn::Result<TokenStream2> {
212
224
  let file = rust_embed_utils::read_file_from_fs(Path::new(full_canonical_path)).expect("File should be readable");
213
225
  let hash = file.metadata.sha256_hash();
214
226
  let last_modified = match file.metadata.last_modified() {
@@ -227,7 +239,11 @@ fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, ful
227
239
  #[cfg(not(feature = "mime-guess"))]
228
240
  let mimetype_tokens = TokenStream2::new();
229
241
 
242
+ let embedding_code = if metadata_only {
243
+ quote! {
244
+ const BYTES: &'static [u8] = &[];
245
+ }
230
- let embedding_code = if cfg!(feature = "compression") {
246
+ } else if cfg!(feature = "compression") {
231
247
  let folder_path = folder_path.ok_or(syn::Error::new(ident.span(), "`folder` must be provided under `compression` feature."))?;
232
248
  // Print some debugging information
233
249
  let full_relative_path = PathBuf::from_iter([folder_path, rel_path]);
@@ -273,6 +289,20 @@ fn find_attribute_values(ast: &syn::DeriveInput, attr_name: &str) -> Vec<String>
273
289
  .collect()
274
290
  }
275
291
 
292
+ fn find_bool_attribute(ast: &syn::DeriveInput, attr_name: &str) -> Option<bool> {
293
+ ast
294
+ .attrs
295
+ .iter()
296
+ .find(|value| value.path().is_ident(attr_name))
297
+ .and_then(|attr| match &attr.meta {
298
+ Meta::NameValue(MetaNameValue {
299
+ value: Expr::Lit(ExprLit { lit: Lit::Bool(val), .. }),
300
+ ..
301
+ }) => Some(val.value()),
302
+ _ => None,
303
+ })
304
+ }
305
+
276
306
  fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
277
307
  match ast.data {
278
308
  Data::Struct(ref data) => match data.fields {
@@ -294,6 +324,7 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
294
324
  let prefix = find_attribute_values(ast, "prefix").into_iter().next();
295
325
  let includes = find_attribute_values(ast, "include");
296
326
  let excludes = find_attribute_values(ast, "exclude");
327
+ let metadata_only = find_bool_attribute(ast, "metadata_only").unwrap_or(false);
297
328
 
298
329
  #[cfg(not(feature = "include-exclude"))]
299
330
  if !includes.is_empty() || !excludes.is_empty() {
@@ -340,10 +371,18 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
340
371
  return Err(syn::Error::new_spanned(ast, message));
341
372
  };
342
373
 
374
+ generate_assets(
375
+ &ast.ident,
343
- generate_assets(&ast.ident, relative_path.as_deref(), absolute_folder_path, prefix, includes, excludes)
376
+ relative_path.as_deref(),
377
+ absolute_folder_path,
378
+ prefix,
379
+ includes,
380
+ excludes,
381
+ metadata_only,
382
+ )
344
383
  }
345
384
 
346
- #[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude))]
385
+ #[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude, metadata_only))]
347
386
  pub fn derive_input_object(input: TokenStream) -> TokenStream {
348
387
  let ast = parse_macro_input!(input as DeriveInput);
349
388
  match impl_rust_embed(&ast) {
readme.md CHANGED
@@ -75,12 +75,18 @@ If the feature `debug-embed` is enabled or the binary compiled in release mode a
75
75
 
76
76
  Otherwise the files are listed from the file system on each call.
77
77
 
78
+ ## Attributes
78
- ## The `prefix` attribute
79
+ ### `prefix`
79
80
 
80
81
  You can add `#[prefix = "my_prefix/"]` to the `RustEmbed` struct to add a prefix
81
82
  to all of the file paths. This prefix will be required on `get` calls, and will
82
83
  be included in the file paths returned by `iter`.
83
84
 
85
+ ### `metadata_only`
86
+
87
+ You can add `#[metadata_only = true]` to the `RustEmbed` struct to exclude file contents from the
88
+ binary. Only file paths and metadata will be embedded.
89
+
84
90
  ## Features
85
91
 
86
92
  ### `debug-embed`
tests/metadata_only.rs ADDED
@@ -0,0 +1,12 @@
1
+ use rust_embed::{EmbeddedFile, RustEmbed};
2
+
3
+ #[derive(RustEmbed)]
4
+ #[folder = "examples/public/"]
5
+ #[metadata_only = true]
6
+ struct Asset;
7
+
8
+ #[test]
9
+ fn file_is_empty() {
10
+ let index_file: EmbeddedFile = Asset::get("index.html").expect("index.html exists");
11
+ assert_eq!(index_file.data.len(), 0);
12
+ }