Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to specify a custom path to the rust_embed crate in generated code #232

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 38 additions & 27 deletions impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenStream2> {
extern crate rust_embed_utils;

Expand All @@ -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 {
Expand Down Expand Up @@ -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)()}
Expand All @@ -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<rust_embed::EmbeddedFile> {
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)] = &[
Expand All @@ -98,18 +98,20 @@ fn embedded(
}

#not_debug_attr
impl rust_embed::RustEmbed for #ident {
fn get(file_path: &str) -> ::std::option::Option<rust_embed::EmbeddedFile> {
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)?; },
Expand Down Expand Up @@ -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();
Comment on lines -144 to -147
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to consider: these are the two places where we use ::rust_embed instead of rust_embed... was this for a particular reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling this is just code style for this particular contributor based on the ::std usage

Copy link
Contributor

@BBaoVanC BBaoVanC May 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that you should always use the :: prefix in proc macros since it is an absolute path--it wouldn't get broken by stuff being renamed or different uses being added under the same name, outside the macro in the end user's surrounding file

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this also need to use #crate_path to be consistent?

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<rust_embed::EmbeddedFile> {
pub fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> {
#handle_prefix

let rel_file_path = file_path.replace("\\", "/");
Expand All @@ -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
}
Expand All @@ -182,27 +184,27 @@ 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<rust_embed::EmbeddedFile> {
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()))
}
}
}
}

fn generate_assets(
ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<String>, includes: Vec<String>, excludes: Vec<String>,
metadata_only: bool,
metadata_only: bool, crate_path: &syn::Path,
) -> syn::Result<TokenStream2> {
let embedded_impl = embedded(
ident,
Expand All @@ -212,20 +214,23 @@ 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
#dynamic_impl
})
}

fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str, metadata_only: bool) -> syn::Result<TokenStream2> {
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<TokenStream2> {
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() {
Expand Down Expand Up @@ -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! {
Expand All @@ -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)
}
}
})
Expand Down Expand Up @@ -317,6 +322,11 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
_ => 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(
Expand Down Expand Up @@ -384,10 +394,11 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
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) {
Expand Down
20 changes: 20 additions & 0 deletions tests/custom_crate_path.rs
Original file line number Diff line number Diff line change
@@ -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;