~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.


32a496df Bernardo

7 years ago
list files implementation create impl crate for proc_macro main crate reexports macro add utils.rs symlinked in both crates for code reuse export get_files method in main crate for use when in debug
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
  }