Skip to content

Commit

Permalink
Allow users to specify a custom path to the rust_embed crate in gener…
Browse files Browse the repository at this point in the history
…ated code
  • Loading branch information
Wulf committed Feb 7, 2024
1 parent ed8faec commit 113801b
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 20 deletions.
73 changes: 53 additions & 20 deletions impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ use std::{
iter::FromIterator,
path::{Path, PathBuf},
};
use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue};
use syn::{parse::Parse, parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue};

fn embedded(
ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String],
crate_path: &syn::Path,
) -> syn::Result<TokenStream2> {
extern crate rust_embed_utils;

Expand Down Expand Up @@ -57,9 +58,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 @@ -70,7 +71,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 @@ -92,18 +93,18 @@ 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]) -> TokenStream2 {
fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], 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 All @@ -128,7 +129,7 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
#[cfg(debug_assertions)]
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

#declare_includes
Expand All @@ -144,8 +145,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
return ::std::option::Option::None;
}

if rust_embed::utils::is_path_included(&rel_file_path, INCLUDES, EXCLUDES) {
rust_embed::utils::read_file_from_fs(&canonical_file_path).ok()
if #crate_path::utils::is_path_included(&rel_file_path, INCLUDES, EXCLUDES) {
#crate_path::utils::read_file_from_fs(&canonical_file_path).ok()
} else {
::std::option::Option::None
}
Expand All @@ -158,26 +159,27 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
#declare_includes
#declare_excludes

rust_embed::utils::get_files(::std::string::String::from(#folder_path), INCLUDES, EXCLUDES)
#crate_path::utils::get_files(::std::string::String::from(#folder_path), INCLUDES, EXCLUDES)
.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>,
crate_path: &syn::Path,
) -> syn::Result<TokenStream2> {
let embedded_impl = embedded(
ident,
Expand All @@ -186,12 +188,13 @@ fn generate_assets(
prefix.as_deref(),
&includes,
&excludes,
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);
let dynamic_impl = dynamic(ident, absolute_folder_path, prefix.as_deref(), &includes, &excludes, crate_path);

Ok(quote! {
#embedded_impl
Expand Down Expand Up @@ -264,6 +267,23 @@ fn find_attribute_values(ast: &syn::DeriveInput, attr_name: &str) -> Vec<String>
.collect()
}

/// Finds all pairs of the `name = ::some::path` attribute from the derive input,
/// returning only the last one found.
fn find_attribute_path<'a>(ast: &'a syn::DeriveInput, attr_name: &str) -> Option<&'a syn::Path> {
let attributes_with_name = ast.attrs.iter().filter(|value| value.path().is_ident(attr_name)).collect::<Vec<_>>();

match attributes_with_name.last() {
Some(attr) => match &attr.meta {
Meta::NameValue(MetaNameValue {
value: Expr::Path(path_expression),
..
}) => Some(&path_expression.path),
_ => None,
},
_ => None,
}
}

fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
match ast.data {
Data::Struct(ref data) => match data.fields {
Expand All @@ -273,6 +293,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 mut 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 @@ -331,10 +356,18 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
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,
&crate_path,
)
}

#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude))]
#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude, 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;

0 comments on commit 113801b

Please sign in to comment.