Skip to content

Commit

Permalink
Merge pull request #241 from ddfisher/master
Browse files Browse the repository at this point in the history
Add `metadata_only` attribute
  • Loading branch information
pyrossh authored Apr 16, 2024
2 parents a735c9e + 4a2a281 commit 1d17cae
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 9 deletions.
55 changes: 47 additions & 8 deletions impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenStream2> {
extern crate rust_embed_utils;

Expand All @@ -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)
Expand Down Expand Up @@ -103,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)?; },
Expand All @@ -121,6 +125,12 @@ 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 })
});

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");

Expand Down Expand Up @@ -154,7 +164,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
}
Expand Down Expand Up @@ -187,6 +197,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<String>, includes: Vec<String>, excludes: Vec<String>,
metadata_only: bool,
) -> syn::Result<TokenStream2> {
let embedded_impl = embedded(
ident,
Expand All @@ -195,20 +206,21 @@ fn generate_assets(
prefix.as_deref(),
&includes,
&excludes,
metadata_only,
);
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, metadata_only);

Ok(quote! {
#embedded_impl
#dynamic_impl
})
}

fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str) -> syn::Result<TokenStream2> {
fn embed_file(folder_path: Option<&str>, ident: &syn::Ident, rel_path: &str, full_canonical_path: &str, metadata_only: bool) -> 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 All @@ -227,7 +239,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]);
Expand Down Expand Up @@ -273,6 +289,20 @@ fn find_attribute_values(ast: &syn::DeriveInput, attr_name: &str) -> Vec<String>
.collect()
}

fn find_bool_attribute(ast: &syn::DeriveInput, attr_name: &str) -> Option<bool> {
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<TokenStream2> {
match ast.data {
Data::Struct(ref data) => match data.fields {
Expand All @@ -294,6 +324,7 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
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() {
Expand Down Expand Up @@ -340,10 +371,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,
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) {
Expand Down
8 changes: 7 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
12 changes: 12 additions & 0 deletions tests/metadata_only.rs
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 1d17cae

Please sign in to comment.