~repos /rust-embed

#rust#proc-macro#http

git clone https://pyrossh.dev/repos/rust-embed.git
Discussions: https://groups.google.com/g/rust-embed-devs

rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.


Files changed (2) hide show
  1. impl/src/lib.rs +53 -30
  2. tests/prefix.rs +24 -0
impl/src/lib.rs CHANGED
@@ -6,9 +6,9 @@ extern crate proc_macro;
6
6
  use proc_macro::TokenStream;
7
7
  use proc_macro2::TokenStream as TokenStream2;
8
8
  use std::{env, path::Path};
9
- use syn::{Data, DeriveInput, Fields, Lit, Meta};
9
+ use syn::{Data, DeriveInput, Fields, Lit, Meta, MetaNameValue};
10
10
 
11
- fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
11
+ fn embedded(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> TokenStream2 {
12
12
  extern crate rust_embed_utils;
13
13
 
14
14
  let mut match_values = Vec::<TokenStream2>::new();
@@ -16,7 +16,12 @@ fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
16
16
 
17
17
  for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(folder_path) {
18
18
  match_values.push(embed_file(&rel_path, &full_canonical_path));
19
+
19
- list_values.push(rel_path);
20
+ list_values.push(if let Some(prefix) = prefix {
21
+ format!("{}{}", prefix, rel_path)
22
+ } else {
23
+ rel_path
24
+ });
20
25
  }
21
26
 
22
27
  let array_len = list_values.len();
@@ -29,10 +34,19 @@ fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
29
34
  quote! { #[cfg(not(debug_assertions))]}
30
35
  };
31
36
 
37
+ let handle_prefix = if let Some(prefix) = prefix {
38
+ quote! {
39
+ let file_path = file_path.strip_prefix(#prefix)?;
40
+ }
41
+ } else {
42
+ TokenStream2::new()
43
+ };
44
+
32
45
  quote! {
33
46
  #not_debug_attr
34
47
  impl #ident {
35
48
  pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
49
+ #handle_prefix
36
50
  match file_path.replace("\\", "/").as_str() {
37
51
  #(#match_values)*
38
52
  _ => None,
@@ -61,7 +75,16 @@ fn embedded(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
61
75
  }
62
76
  }
63
77
 
64
- fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
78
+ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> TokenStream2 {
79
+ let (handle_prefix, map_iter) = if let Some(prefix) = prefix {
80
+ (
81
+ quote! { let file_path = file_path.strip_prefix(#prefix)?; },
82
+ quote! { std::borrow::Cow::Owned(format!("{}{}", #prefix, e.rel_path)) },
83
+ )
84
+ } else {
85
+ (TokenStream2::new(), quote! { std::borrow::Cow::from(e.rel_path) })
86
+ };
87
+
65
88
  quote! {
66
89
  #[cfg(debug_assertions)]
67
90
  impl #ident {
@@ -69,6 +92,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
69
92
  use std::fs;
70
93
  use std::path::Path;
71
94
 
95
+ #handle_prefix
96
+
72
97
  let file_path = Path::new(#folder_path).join(file_path.replace("\\", "/"));
73
98
  match fs::read(file_path) {
74
99
  Ok(contents) => Some(std::borrow::Cow::from(contents)),
@@ -80,7 +105,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
80
105
 
81
106
  pub fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
82
107
  use std::path::Path;
83
- rust_embed::utils::get_files(String::from(#folder_path)).map(|e| std::borrow::Cow::from(e.rel_path))
108
+ rust_embed::utils::get_files(String::from(#folder_path))
109
+ .map(|e| #map_iter)
84
110
  }
85
111
  }
86
112
 
@@ -97,13 +123,13 @@ fn dynamic(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
97
123
  }
98
124
  }
99
125
 
100
- fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
126
+ fn generate_assets(ident: &syn::Ident, folder_path: String, prefix: Option<String>) -> TokenStream2 {
101
- let embedded_impl = embedded(ident, folder_path.clone());
127
+ let embedded_impl = embedded(ident, folder_path.clone(), prefix.as_deref());
102
128
  if cfg!(feature = "debug-embed") {
103
129
  return embedded_impl;
104
130
  }
105
131
 
106
- let dynamic_impl = dynamic(ident, folder_path);
132
+ let dynamic_impl = dynamic(ident, folder_path, prefix.as_deref());
107
133
 
108
134
  quote! {
109
135
  #embedded_impl
@@ -112,9 +138,9 @@ fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
112
138
  }
113
139
 
114
140
  #[cfg(not(feature = "compression"))]
115
- fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
141
+ fn embed_file(match_str: &str, full_canonical_path: &str) -> TokenStream2 {
116
142
  quote! {
117
- #rel_path => {
143
+ #match_str => {
118
144
  let bytes = &include_bytes!(#full_canonical_path)[..];
119
145
  Some(std::borrow::Cow::from(bytes))
120
146
  },
@@ -133,6 +159,19 @@ fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
133
159
  }
134
160
  }
135
161
 
162
+ /// Find a `name = "value"` attribute from the derive input
163
+ fn find_attribute_value(ast: &syn::DeriveInput, attr_name: &str) -> Option<String> {
164
+ ast
165
+ .attrs
166
+ .iter()
167
+ .find(|value| value.path.is_ident(attr_name))
168
+ .and_then(|attr| attr.parse_meta().ok())
169
+ .and_then(|meta| match meta {
170
+ Meta::NameValue(MetaNameValue { lit: Lit::Str(val), .. }) => Some(val.value()),
171
+ _ => None,
172
+ })
173
+ }
174
+
136
175
  fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
137
176
  match ast.data {
138
177
  Data::Struct(ref data) => match data.fields {
@@ -142,24 +181,8 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
142
181
  _ => panic!("RustEmbed can only be derived for unit structs"),
143
182
  };
144
183
 
145
- let attribute = ast
146
- .attrs
147
- .iter()
148
- .find(|value| value.path.is_ident("folder"))
149
- .expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
150
- let meta = attribute
151
- .parse_meta()
152
- .expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
153
- let literal_value = match meta {
154
- Meta::NameValue(ref data) => &data.lit,
155
- _ => panic!("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]"),
184
+ let folder_path = find_attribute_value(ast, "folder").expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
156
- };
157
- let folder_path = match literal_value {
158
- Lit::Str(ref val) => val.clone().value(),
159
- _ => {
160
- panic!("#[derive(RustEmbed)] attribute value must be a string literal");
185
+ let prefix = find_attribute_value(ast, "prefix");
161
- }
162
- };
163
186
 
164
187
  #[cfg(feature = "interpolate-folder-path")]
165
188
  let folder_path = shellexpand::full(&folder_path).unwrap().to_string();
@@ -192,10 +215,10 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
192
215
  panic!(message);
193
216
  };
194
217
 
195
- generate_assets(&ast.ident, folder_path)
218
+ generate_assets(&ast.ident, folder_path, prefix)
196
219
  }
197
220
 
198
- #[proc_macro_derive(RustEmbed, attributes(folder))]
221
+ #[proc_macro_derive(RustEmbed, attributes(folder, prefix))]
199
222
  pub fn derive_input_object(input: TokenStream) -> TokenStream {
200
223
  let ast: DeriveInput = syn::parse(input).unwrap();
201
224
  let gen = impl_rust_embed(&ast);
tests/prefix.rs ADDED
@@ -0,0 +1,24 @@
1
+ use rust_embed::RustEmbed;
2
+
3
+ #[derive(RustEmbed)]
4
+ #[folder = "examples/public/"]
5
+ #[prefix = "prefix/"]
6
+ struct Asset;
7
+
8
+ #[test]
9
+ fn get_with_prefix() {
10
+ assert!(Asset::get("prefix/index.html").is_some());
11
+ }
12
+
13
+ #[test]
14
+ fn get_without_prefix() {
15
+ assert!(Asset::get("index.html").is_none());
16
+ }
17
+
18
+ #[test]
19
+ fn iter_values_have_prefix() {
20
+ for file in Asset::iter() {
21
+ assert!(file.starts_with("prefix/"));
22
+ assert!(Asset::get(file.as_ref()).is_some());
23
+ }
24
+ }