From 9c65ce096ae328918328ec35d2a0654ce7890654 Mon Sep 17 00:00:00 2001 From: Day Fisher Date: Fri, 8 Mar 2024 17:39:18 -0800 Subject: [PATCH 1/4] simple metadata_only --- impl/src/lib.rs | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/impl/src/lib.rs b/impl/src/lib.rs index cedb14b..4de8eb4 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -16,6 +16,7 @@ use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta fn embedded( ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], + metadata_only: bool, ) -> syn::Result { extern crate rust_embed_utils; @@ -25,7 +26,10 @@ fn embedded( let includes: Vec<&str> = includes.iter().map(AsRef::as_ref).collect(); let excludes: Vec<&str> = excludes.iter().map(AsRef::as_ref).collect(); for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(absolute_folder_path.clone(), &includes, &excludes) { - match_values.insert(rel_path.clone(), embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path)?); + match_values.insert( + rel_path.clone(), + embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path, metadata_only)?, + ); list_values.push(if let Some(prefix) = prefix { format!("{}{}", prefix, rel_path) @@ -187,6 +191,7 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ fn generate_assets( ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option, includes: Vec, excludes: Vec, + metadata_only: bool, ) -> syn::Result { let embedded_impl = embedded( ident, @@ -195,6 +200,7 @@ fn generate_assets( prefix.as_deref(), &includes, &excludes, + metadata_only, ); if cfg!(feature = "debug-embed") { return embedded_impl; @@ -208,7 +214,7 @@ fn generate_assets( }) } -fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str) -> syn::Result { +fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str, metadata_only: bool) -> syn::Result { let file = rust_embed_utils::read_file_from_fs(Path::new(full_canonical_path)).expect("File should be readable"); let hash = file.metadata.sha256_hash(); let last_modified = match file.metadata.last_modified() { @@ -227,7 +233,11 @@ fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, ful #[cfg(not(feature = "mime-guess"))] let mimetype_tokens = TokenStream2::new(); - let embedding_code = if cfg!(feature = "compression") { + let embedding_code = if metadata_only { + quote! { + const BYTES: &'static [u8] = &[]; + } + } else if cfg!(feature = "compression") { let folder_path = folder_path.ok_or(syn::Error::new(ident.span(), "`folder` must be provided under `compression` feature."))?; // Print some debugging information let full_relative_path = PathBuf::from_iter([folder_path, rel_path]); @@ -273,6 +283,20 @@ fn find_attribute_values(ast: &syn::DeriveInput, attr_name: &str) -> Vec .collect() } +fn find_bool_attribute(ast: &syn::DeriveInput, attr_name: &str) -> Option { + ast + .attrs + .iter() + .find(|value| value.path().is_ident(attr_name)) + .and_then(|attr| match &attr.meta { + Meta::NameValue(MetaNameValue { + value: Expr::Lit(ExprLit { lit: Lit::Bool(val), .. }), + .. + }) => Some(val.value()), + _ => None, + }) +} + fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result { match ast.data { Data::Struct(ref data) => match data.fields { @@ -294,6 +318,7 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result { let prefix = find_attribute_values(ast, "prefix").into_iter().next(); let includes = find_attribute_values(ast, "include"); let excludes = find_attribute_values(ast, "exclude"); + let metadata_only = find_bool_attribute(ast, "metadata_only").unwrap_or(false); #[cfg(not(feature = "include-exclude"))] if !includes.is_empty() || !excludes.is_empty() { @@ -340,10 +365,18 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result { return Err(syn::Error::new_spanned(ast, message)); }; - generate_assets(&ast.ident, relative_path.as_deref(), absolute_folder_path, prefix, includes, excludes) + generate_assets( + &ast.ident, + relative_path.as_deref(), + absolute_folder_path, + prefix, + includes, + excludes, + metadata_only, + ) } -#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude))] +#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude, metadata_only))] pub fn derive_input_object(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); match impl_rust_embed(&ast) { From 287a73fe2b527e572c037c6f93713695af805076 Mon Sep 17 00:00:00 2001 From: Day Fisher Date: Mon, 11 Mar 2024 13:46:11 -0700 Subject: [PATCH 2/4] Add metadata_only to dynamic mode; add test --- impl/src/lib.rs | 10 +++++++--- tests/metadata_only.rs | 12 ++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 tests/metadata_only.rs diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 4de8eb4..18b531b 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -107,7 +107,7 @@ fn embedded( }) } -fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String]) -> TokenStream2 { +fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool) -> TokenStream2 { let (handle_prefix, map_iter) = if let ::std::option::Option::Some(prefix) = prefix { ( quote! { let file_path = file_path.strip_prefix(#prefix)?; }, @@ -125,6 +125,10 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ const EXCLUDES: &[&str] = &[#(#excludes),*]; }; + let strip_contents = metadata_only.then_some(quote! { + .map(|mut file| { file.data = ::std::default::Default::default(); file }) + }); + let canonical_folder_path = Path::new(&folder_path).canonicalize().expect("folder path must resolve to an absolute path"); let canonical_folder_path = canonical_folder_path.to_str().expect("absolute folder path must be valid unicode"); @@ -158,7 +162,7 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ } if rust_embed::utils::is_path_included(&rel_file_path, INCLUDES, EXCLUDES) { - rust_embed::utils::read_file_from_fs(&canonical_file_path).ok() + rust_embed::utils::read_file_from_fs(&canonical_file_path).ok() #strip_contents } else { ::std::option::Option::None } @@ -206,7 +210,7 @@ fn generate_assets( return embedded_impl; } let embedded_impl = embedded_impl?; - let dynamic_impl = dynamic(ident, absolute_folder_path, prefix.as_deref(), &includes, &excludes); + let dynamic_impl = dynamic(ident, absolute_folder_path, prefix.as_deref(), &includes, &excludes, metadata_only); Ok(quote! { #embedded_impl diff --git a/tests/metadata_only.rs b/tests/metadata_only.rs new file mode 100644 index 0000000..77a7117 --- /dev/null +++ b/tests/metadata_only.rs @@ -0,0 +1,12 @@ +use rust_embed::{EmbeddedFile, RustEmbed}; + +#[derive(RustEmbed)] +#[folder = "examples/public/"] +#[metadata_only = true] +struct Asset; + +#[test] +fn file_is_empty() { + let index_file: EmbeddedFile = Asset::get("index.html").expect("index.html exists"); + assert_eq!(index_file.data.len(), 0); +} From 4ac6f5b3d5a9f372383611f19cce0a4fd9608083 Mon Sep 17 00:00:00 2001 From: Day Fisher Date: Tue, 9 Apr 2024 14:28:36 -0700 Subject: [PATCH 3/4] add docs --- readme.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1659b0c..94e243c 100644 --- a/readme.md +++ b/readme.md @@ -75,12 +75,18 @@ If the feature `debug-embed` is enabled or the binary compiled in release mode a Otherwise the files are listed from the file system on each call. -## The `prefix` attribute +## Attributes +### `prefix` You can add `#[prefix = "my_prefix/"]` to the `RustEmbed` struct to add a prefix to all of the file paths. This prefix will be required on `get` calls, and will be included in the file paths returned by `iter`. +### `metadata_only` + +You can add `#[metadata_only = true]` to the `RustEmbed` struct to exclude file contents from the +binary. Only file paths and metadata will be embedded. + ## Features ### `debug-embed` From 4a2a281cbf7c0e7a5d1d9f7b35d6da2df91cec0d Mon Sep 17 00:00:00 2001 From: Day Fisher Date: Wed, 10 Apr 2024 18:49:15 -0700 Subject: [PATCH 4/4] add small comment --- impl/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 18b531b..1568e7d 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -125,6 +125,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ const EXCLUDES: &[&str] = &[#(#excludes),*]; }; + // In metadata_only mode, we still need to read file contents to generate the file hash, but + // then we drop the file data. let strip_contents = metadata_only.then_some(quote! { .map(|mut file| { file.data = ::std::default::Default::default(); file }) });