~repos /rust-embed

#rust#proc-macro#http

git clone https://pyrossh.dev/repos/rust-embed.git

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


b3d8cb00 Peter

4 years ago
Merge pull request #133 from pyros2097/feature/prefix
Files changed (3) hide show
  1. impl/src/lib.rs +51 -28
  2. readme.md +7 -1
  3. 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
@@ -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);
readme.md CHANGED
@@ -72,6 +72,11 @@ If the feature `debug-embed` is enabled or the binary compiled in release mode a
72
72
 
73
73
  Otherwise the files are listed from the file system on each call.
74
74
 
75
+ ## The `prefix` attribute
76
+ You can add `#[prefix = "my_prefix/"]` to the `RustEmbed` struct to add a prefix
77
+ to all of the file paths. This prefix will be required on `get` calls, and will
78
+ be included in the file paths returned by `iter`.
79
+
75
80
  ## Features
76
81
 
77
82
  ### `debug-embed`
@@ -101,10 +106,11 @@ use rust_embed::RustEmbed;
101
106
 
102
107
  #[derive(RustEmbed)]
103
108
  #[folder = "examples/public/"]
109
+ #[prefix = "prefix/"]
104
110
  struct Asset;
105
111
 
106
112
  fn main() {
107
- let index_html = Asset::get("index.html").unwrap();
113
+ let index_html = Asset::get("prefix/index.html").unwrap();
108
114
  println!("{:?}", std::str::from_utf8(index_html.as_ref()));
109
115
 
110
116
  for file in Asset::iter() {
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
+ }