~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 (7) hide show
  1. Cargo.toml +1 -5
  2. impl/Cargo.toml +19 -0
  3. impl/src/lib.rs +135 -0
  4. impl/src/utils.rs +1 -0
  5. src/lib.rs +7 -129
  6. src/utils.rs +38 -0
  7. tests/lib.rs +14 -0
Cargo.toml CHANGED
@@ -10,13 +10,9 @@ keywords = ["http", "rocket", "static", "web", "server"]
10
10
  categories = ["web-programming::http-server"]
11
11
  authors = ["pyros2097 <pyros2097@gmail.com>"]
12
12
 
13
- [lib]
14
- proc-macro = true
15
-
16
13
  [dependencies]
17
- syn = "0.11"
18
- quote = "0.3"
19
14
  walkdir = "2.1.4"
15
+ rust-embed-impl = { version = "4.0.0", path = "impl"}
20
16
 
21
17
  actix-web = { version = "0.7", optional = true }
22
18
  mime_guess = { version = "2.0.0-alpha.6", optional = true }
impl/Cargo.toml ADDED
@@ -0,0 +1,19 @@
1
+ [package]
2
+ name = "rust-embed-impl"
3
+ version = "4.0.0"
4
+ description = "Rust Custom Derive Macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev"
5
+ readme = "readme.md"
6
+ documentation = "https://docs.rs/rust-embed"
7
+ repository = "https://github.com/pyros2097/rust-embed"
8
+ license = "MIT"
9
+ keywords = ["http", "rocket", "static", "web", "server"]
10
+ categories = ["web-programming::http-server"]
11
+ authors = ["pyros2097 <pyros2097@gmail.com>"]
12
+
13
+ [lib]
14
+ proc-macro = true
15
+
16
+ [dependencies]
17
+ syn = "0.11"
18
+ quote = "0.3"
19
+ walkdir = "2.1.4"
impl/src/lib.rs ADDED
@@ -0,0 +1,135 @@
1
+ #![recursion_limit = "1024"]
2
+ extern crate proc_macro;
3
+ #[macro_use]
4
+ extern crate quote;
5
+ extern crate syn;
6
+
7
+ extern crate walkdir;
8
+
9
+ use proc_macro::TokenStream;
10
+ use quote::Tokens;
11
+ use std::path::Path;
12
+ use syn::*;
13
+
14
+ mod utils;
15
+
16
+ #[cfg(all(debug_assertions, not(feature = "debug-embed")))]
17
+ fn generate_assets(ident: &syn::Ident, folder_path: String) -> quote::Tokens {
18
+ quote!{
19
+ impl #ident {
20
+ pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>> {
21
+ use std::fs::File;
22
+ use std::io::Read;
23
+ use std::path::Path;
24
+
25
+ let folder_path = #folder_path;
26
+ let name = &format!("{}{}", folder_path, file_path);
27
+ let path = &Path::new(name);
28
+ let mut file = match File::open(path) {
29
+ Ok(mut file) => file,
30
+ Err(_e) => {
31
+ return None
32
+ }
33
+ };
34
+ let mut data: Vec<u8> = Vec::new();
35
+ match file.read_to_end(&mut data) {
36
+ Ok(_) => Some(data),
37
+ Err(_e) => {
38
+ return None
39
+ }
40
+ }
41
+ }
42
+
43
+ pub fn iter() -> impl Iterator<Item = impl AsRef<str>> {
44
+ use rust_embed::utils::get_files;
45
+ get_files(String::from(#folder_path)).map(|e| e.rel_path)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ #[cfg(any(not(debug_assertions), feature = "debug-embed"))]
52
+ fn generate_assets(ident: &syn::Ident, folder_path: String) -> quote::Tokens {
53
+ use utils::{get_files, FileEntry};
54
+
55
+ let mut match_values = Vec::<Tokens>::new();
56
+ let mut list_values = Vec::<String>::new();
57
+
58
+ for FileEntry { rel_path, full_canonical_path } in get_files(folder_path) {
59
+ match_values.push(quote!{
60
+ #rel_path => Some(&include_bytes!(#full_canonical_path)[..]),
61
+ });
62
+ list_values.push(rel_path);
63
+ }
64
+
65
+ let array_len = list_values.len();
66
+
67
+ quote!{
68
+ impl #ident {
69
+ pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>> {
70
+ match file_path {
71
+ #(#match_values)*
72
+ _ => None,
73
+ }
74
+ }
75
+
76
+ pub fn iter() -> impl Iterator<Item = impl AsRef<str>> {
77
+ static items: [&str; #array_len] = [#(#list_values),*];
78
+ items.iter()
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ fn help() {
85
+ panic!("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
86
+ }
87
+
88
+ fn impl_rust_embed(ast: &syn::DeriveInput) -> Tokens {
89
+ match ast.body {
90
+ Body::Enum(_) => help(),
91
+ Body::Struct(ref data) => match data {
92
+ &VariantData::Struct(_) => help(),
93
+ _ => {}
94
+ },
95
+ };
96
+ let ident = &ast.ident;
97
+ if ast.attrs.len() == 0 || ast.attrs.len() > 1 {
98
+ help();
99
+ }
100
+ let value = &ast.attrs[0].value;
101
+ let literal_value = match value {
102
+ &MetaItem::NameValue(ref attr_name, ref value) => {
103
+ if attr_name == "folder" {
104
+ value
105
+ } else {
106
+ panic!("#[derive(RustEmbed)] attribute name must be folder");
107
+ }
108
+ }
109
+ _ => {
110
+ panic!("#[derive(RustEmbed)] attribute name must be folder");
111
+ }
112
+ };
113
+ let folder_path = match literal_value {
114
+ &Lit::Str(ref val, _) => val.clone(),
115
+ _ => {
116
+ panic!("#[derive(RustEmbed)] attribute value must be a string literal");
117
+ }
118
+ };
119
+ if !Path::new(&folder_path).exists() {
120
+ panic!(
121
+ "#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
122
+ folder_path,
123
+ std::env::current_dir().unwrap().to_str().unwrap()
124
+ );
125
+ };
126
+ generate_assets(ident, folder_path)
127
+ }
128
+
129
+ #[proc_macro_derive(RustEmbed, attributes(folder))]
130
+ pub fn derive_input_object(input: TokenStream) -> TokenStream {
131
+ let s = input.to_string();
132
+ let ast = syn::parse_derive_input(&s).unwrap();
133
+ let gen = impl_rust_embed(&ast);
134
+ gen.parse().unwrap()
135
+ }
impl/src/utils.rs ADDED
@@ -0,0 +1 @@
1
+ ../../src/utils.rs
src/lib.rs CHANGED
@@ -1,133 +1,11 @@
1
- #![recursion_limit = "1024"]
1
+ #[cfg(all(debug_assertions, not(feature = "debug-embed")))]
2
- extern crate proc_macro;
3
- #[macro_use]
4
- extern crate quote;
5
- extern crate syn;
6
-
7
2
  extern crate walkdir;
8
3
 
4
+ #[allow(unused_imports)]
5
+ #[macro_use]
9
- use proc_macro::TokenStream;
6
+ extern crate rust_embed_impl;
10
- use quote::Tokens;
7
+ pub use rust_embed_impl::*;
11
- use std::path::Path;
12
- use syn::*;
13
8
 
9
+ #[doc(hidden)]
14
10
  #[cfg(all(debug_assertions, not(feature = "debug-embed")))]
15
- fn generate_assets(ident: &syn::Ident, folder_path: String) -> quote::Tokens {
16
- quote!{
17
- impl #ident {
18
- pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>> {
19
- use std::fs::File;
11
+ pub mod utils;
20
- use std::io::Read;
21
- use std::path::Path;
22
-
23
- let folder_path = #folder_path;
24
- let name = &format!("{}{}", folder_path, file_path);
25
- let path = &Path::new(name);
26
- let mut file = match File::open(path) {
27
- Ok(mut file) => file,
28
- Err(_e) => {
29
- return None
30
- }
31
- };
32
- let mut data: Vec<u8> = Vec::new();
33
- match file.read_to_end(&mut data) {
34
- Ok(_) => Some(data),
35
- Err(_e) => {
36
- return None
37
- }
38
- }
39
- }
40
- }
41
- }
42
- }
43
-
44
- #[cfg(any(not(debug_assertions), feature = "debug-embed"))]
45
- fn generate_assets(ident: &syn::Ident, folder_path: String) -> quote::Tokens {
46
- use walkdir::WalkDir;
47
- let mut values = Vec::<Tokens>::new();
48
- for entry in WalkDir::new(folder_path.clone())
49
- .into_iter()
50
- .filter_map(|e| e.ok())
51
- .filter(|e| e.file_type().is_file())
52
- {
53
- let base = &folder_path.clone();
54
- let key = String::from(
55
- entry
56
- .path()
57
- .strip_prefix(base)
58
- .unwrap()
59
- .to_str()
60
- .expect("Path does not have a string representation"),
61
- );
62
- let canonical_path = std::fs::canonicalize(entry.path()).expect("Could not get canonical path");
63
- let key = if std::path::MAIN_SEPARATOR == '\\' { key.replace('\\', "/") } else { key };
64
- let canonical_path_str = canonical_path.to_str();
65
- let value = quote!{
66
- #key => Some(&include_bytes!(#canonical_path_str)[..]),
67
- };
68
- values.push(value);
69
- }
70
- quote!{
71
- impl #ident {
72
- pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>> {
73
- match file_path {
74
- #(#values)*
75
- _ => None,
76
- }
77
- }
78
- }
79
- }
80
- }
81
-
82
- fn help() {
83
- panic!("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
84
- }
85
-
86
- fn impl_rust_embed(ast: &syn::DeriveInput) -> Tokens {
87
- match ast.body {
88
- Body::Enum(_) => help(),
89
- Body::Struct(ref data) => match data {
90
- &VariantData::Struct(_) => help(),
91
- _ => {}
92
- },
93
- };
94
- let ident = &ast.ident;
95
- if ast.attrs.len() == 0 || ast.attrs.len() > 1 {
96
- help();
97
- }
98
- let value = &ast.attrs[0].value;
99
- let literal_value = match value {
100
- &MetaItem::NameValue(ref attr_name, ref value) => {
101
- if attr_name == "folder" {
102
- value
103
- } else {
104
- panic!("#[derive(RustEmbed)] attribute name must be folder");
105
- }
106
- }
107
- _ => {
108
- panic!("#[derive(RustEmbed)] attribute name must be folder");
109
- }
110
- };
111
- let folder_path = match literal_value {
112
- &Lit::Str(ref val, _) => val.clone(),
113
- _ => {
114
- panic!("#[derive(RustEmbed)] attribute value must be a string literal");
115
- }
116
- };
117
- if !Path::new(&folder_path).exists() {
118
- panic!(
119
- "#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
120
- folder_path,
121
- std::env::current_dir().unwrap().to_str().unwrap()
122
- );
123
- };
124
- generate_assets(ident, folder_path)
125
- }
126
-
127
- #[proc_macro_derive(RustEmbed, attributes(folder))]
128
- pub fn derive_input_object(input: TokenStream) -> TokenStream {
129
- let s = input.to_string();
130
- let ast = syn::parse_derive_input(&s).unwrap();
131
- let gen = impl_rust_embed(&ast);
132
- gen.parse().unwrap()
133
- }
src/utils.rs ADDED
@@ -0,0 +1,38 @@
1
+ #[cfg_attr(all(debug_assertions, not(feature = "debug-embed")), allow(unused))]
2
+ pub struct FileEntry {
3
+ pub rel_path: String,
4
+ pub full_canonical_path: String,
5
+ }
6
+
7
+ #[cfg_attr(all(debug_assertions, not(feature = "debug-embed")), allow(unused))]
8
+ pub fn get_files(folder_path: String) -> impl Iterator<Item = FileEntry> {
9
+ use std;
10
+ use walkdir;
11
+ walkdir::WalkDir::new(&folder_path)
12
+ .into_iter()
13
+ .filter_map(|e| e.ok())
14
+ .filter(|e| e.file_type().is_file())
15
+ .map(move |e| {
16
+ let rel_path = e
17
+ .path()
18
+ .strip_prefix(&folder_path)
19
+ .unwrap()
20
+ .to_str()
21
+ .expect("Path does not have a string representation")
22
+ .to_owned();
23
+
24
+ let full_canonical_path = std::fs::canonicalize(e.path())
25
+ .expect("Could not get canonical path")
26
+ .to_str()
27
+ .expect("Path does not have a string representation")
28
+ .to_owned();
29
+
30
+ let rel_path = if std::path::MAIN_SEPARATOR == '\\' {
31
+ rel_path.replace('\\', "/")
32
+ } else {
33
+ rel_path
34
+ };
35
+
36
+ FileEntry { rel_path, full_canonical_path }
37
+ })
38
+ }
tests/lib.rs CHANGED
@@ -20,6 +20,13 @@ fn dev() {
20
20
  None => assert!(false, "llama.png should exist"),
21
21
  _ => assert!(true),
22
22
  }
23
+
24
+ let mut num_files = 0;
25
+ for file in Asset::iter() {
26
+ assert!(Asset::get(file.as_ref()).is_some());
27
+ num_files += 1;
28
+ }
29
+ assert_eq!(num_files, 6);
23
30
  }
24
31
 
25
32
  #[test]
@@ -41,4 +48,11 @@ fn prod() {
41
48
  None => assert!(false, "llama.png should exist"),
42
49
  _ => assert!(true),
43
50
  }
51
+
52
+ let mut num_files = 0;
53
+ for file in Asset::iter() {
54
+ assert!(Asset::get(file.as_ref()).is_some());
55
+ num_files += 1;
56
+ }
57
+ assert_eq!(num_files, 6);
44
58
  }