From e1f172eee61d9bf39a644a7d41411995fab84cb7 Mon Sep 17 00:00:00 2001 From: Haris Khan <4259838+Wulf@users.noreply.github.com> Date: Sat, 18 May 2024 22:36:00 -0400 Subject: [PATCH] Allow users to specify a custom path to the rust_embed crate in generated code --- .github/workflows/test.yml | 2 ++ impl/src/lib.rs | 65 ++++++++++++++++++++++---------------- tests/custom_crate_path.rs | 20 ++++++++++++ 3 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 tests/custom_crate_path.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d96dfc..5d234c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,6 +37,8 @@ jobs: cargo test --test mime_guess --features "mime-guess" --release cargo test --test interpolated_path --features "interpolate-folder-path" cargo test --test interpolated_path --features "interpolate-folder-path" --release + cargo test --test custom_crate_path + cargo test --test custom_crate_path --release cargo build --example basic cargo build --example rocket --features rocket cargo build --example actix --features actix diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 43349a0..4954717 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -17,7 +17,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, + metadata_only: bool, crate_path: &syn::Path, ) -> syn::Result { extern crate rust_embed_utils; @@ -30,7 +30,7 @@ fn embedded( for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(absolute_folder_path.clone(), matcher) { match_values.insert( rel_path.clone(), - embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path, metadata_only)?, + embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path, metadata_only, crate_path)?, ); list_values.push(if let Some(prefix) = prefix { @@ -63,9 +63,9 @@ fn embedded( } }); let value_type = if cfg!(feature = "compression") { - quote! { fn() -> rust_embed::EmbeddedFile } + quote! { fn() -> #crate_path::EmbeddedFile } } else { - quote! { rust_embed::EmbeddedFile } + quote! { #crate_path::EmbeddedFile } }; let get_value = if cfg!(feature = "compression") { quote! {|idx| (ENTRIES[idx].1)()} @@ -76,7 +76,7 @@ fn embedded( #not_debug_attr impl #ident { /// Get an embedded file and its metadata. - pub fn get(file_path: &str) -> ::std::option::Option { + pub fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> { #handle_prefix let key = file_path.replace("\\", "/"); const ENTRIES: &'static [(&'static str, #value_type)] = &[ @@ -98,18 +98,20 @@ fn embedded( } #not_debug_attr - impl rust_embed::RustEmbed for #ident { - fn get(file_path: &str) -> ::std::option::Option { + impl #crate_path::RustEmbed for #ident { + fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> { #ident::get(file_path) } - fn iter() -> rust_embed::Filenames { - rust_embed::Filenames::Embedded(#ident::names()) + fn iter() -> #crate_path::Filenames { + #crate_path::Filenames::Embedded(#ident::names()) } } }) } -fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool) -> TokenStream2 { +fn dynamic( + ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool, crate_path: &syn::Path, +) -> TokenStream2 { let (handle_prefix, map_iter) = if let ::std::option::Option::Some(prefix) = prefix { ( quote! { let file_path = file_path.strip_prefix(#prefix)?; }, @@ -141,14 +143,14 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ impl #ident { - fn matcher() -> ::rust_embed::utils::PathMatcher { + fn matcher() -> #crate_path::utils::PathMatcher { #declare_includes #declare_excludes - static PATH_MATCHER: ::std::sync::OnceLock<::rust_embed::utils::PathMatcher> = ::std::sync::OnceLock::new(); - PATH_MATCHER.get_or_init(|| rust_embed::utils::PathMatcher::new(INCLUDES, EXCLUDES)).clone() + static PATH_MATCHER: ::std::sync::OnceLock<#crate_path::utils::PathMatcher> = ::std::sync::OnceLock::new(); + PATH_MATCHER.get_or_init(|| #crate_path::utils::PathMatcher::new(INCLUDES, EXCLUDES)).clone() } /// Get an embedded file and its metadata. - pub fn get(file_path: &str) -> ::std::option::Option { + pub fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> { #handle_prefix let rel_file_path = file_path.replace("\\", "/"); @@ -171,7 +173,7 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ } let path_matcher = Self::matcher(); if path_matcher.is_path_included(&rel_file_path) { - rust_embed::utils::read_file_from_fs(&canonical_file_path).ok() #strip_contents + #crate_path::utils::read_file_from_fs(&canonical_file_path).ok() #strip_contents } else { ::std::option::Option::None } @@ -182,19 +184,19 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ use ::std::path::Path; - rust_embed::utils::get_files(::std::string::String::from(#folder_path), Self::matcher()) + #crate_path::utils::get_files(::std::string::String::from(#folder_path), Self::matcher()) .map(|e| #map_iter) } } #[cfg(debug_assertions)] - impl rust_embed::RustEmbed for #ident { - fn get(file_path: &str) -> ::std::option::Option { + impl #crate_path::RustEmbed for #ident { + fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> { #ident::get(file_path) } - fn iter() -> rust_embed::Filenames { + fn iter() -> #crate_path::Filenames { // the return type of iter() is unnamable, so we have to box it - rust_embed::Filenames::Dynamic(::std::boxed::Box::new(#ident::iter())) + #crate_path::Filenames::Dynamic(::std::boxed::Box::new(#ident::iter())) } } } @@ -202,7 +204,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, + metadata_only: bool, crate_path: &syn::Path, ) -> syn::Result { let embedded_impl = embedded( ident, @@ -212,12 +214,13 @@ fn generate_assets( &includes, &excludes, metadata_only, + crate_path, ); if cfg!(feature = "debug-embed") { return embedded_impl; } let embedded_impl = embedded_impl?; - let dynamic_impl = dynamic(ident, absolute_folder_path, prefix.as_deref(), &includes, &excludes, metadata_only); + let dynamic_impl = dynamic(ident, absolute_folder_path, prefix.as_deref(), &includes, &excludes, metadata_only, crate_path); Ok(quote! { #embedded_impl @@ -225,7 +228,9 @@ fn generate_assets( }) } -fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str, metadata_only: bool) -> syn::Result { +fn embed_file( + folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str, metadata_only: bool, crate_path: &syn::Path, +) -> 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() { @@ -254,7 +259,7 @@ fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, ful let full_relative_path = PathBuf::from_iter([folder_path, rel_path]); let full_relative_path = full_relative_path.to_string_lossy(); quote! { - rust_embed::flate!(static BYTES: [u8] from #full_relative_path); + #crate_path::flate!(static BYTES: [u8] from #full_relative_path); } } else { quote! { @@ -270,9 +275,9 @@ fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, ful #closure_args { #embedding_code - rust_embed::EmbeddedFile { + #crate_path::EmbeddedFile { data: ::std::borrow::Cow::Borrowed(&BYTES), - metadata: rust_embed::Metadata::__rust_embed_new([#(#hash),*], #last_modified, #created #mimetype_tokens) + metadata: #crate_path::Metadata::__rust_embed_new([#(#hash),*], #last_modified, #created #mimetype_tokens) } } }) @@ -317,6 +322,11 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result { _ => return Err(syn::Error::new_spanned(ast, "RustEmbed can only be derived for unit structs")), }; + let crate_path: syn::Path = find_attribute_values(ast, "crate_path") + .last() + .map(|v| syn::parse_str(&v).unwrap()) + .unwrap_or_else(|| syn::parse_str("rust_embed").unwrap()); + let mut folder_paths = find_attribute_values(ast, "folder"); if folder_paths.len() != 1 { return Err(syn::Error::new_spanned( @@ -384,10 +394,11 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result { includes, excludes, metadata_only, + &crate_path, ) } -#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude, metadata_only))] +#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude, metadata_only, crate_path))] pub fn derive_input_object(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); match impl_rust_embed(&ast) { diff --git a/tests/custom_crate_path.rs b/tests/custom_crate_path.rs new file mode 100644 index 0000000..72368ef --- /dev/null +++ b/tests/custom_crate_path.rs @@ -0,0 +1,20 @@ +/// This test checks that the `crate_path` attribute can be used +/// to specify a custom path to the `rust_embed` crate. + +mod custom { + pub mod path { + pub use rust_embed; + } +} + +// We introduce a 'rust_embed' module here to break compilation in case +// the `rust_embed` crate is not loaded correctly. +// +// To test this, try commenting out the attribute which specifies the +// the custom crate path -- you should find that the test fails to compile. +mod rust_embed {} + +#[derive(custom::path::rust_embed::RustEmbed)] +#[crate_path = "custom::path::rust_embed"] +#[folder = "examples/public/"] +struct Asset;