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


72ec32c6 Peter

7 years ago
Merge pull request #45 from vemoo/list-files
Files changed (9) hide show
  1. Cargo.toml +2 -6
  2. appveyor.yml +6 -0
  3. impl/Cargo.toml +19 -0
  4. impl/src/lib.rs +137 -0
  5. impl/src/utils.rs +1 -0
  6. readme.md +49 -3
  7. src/lib.rs +7 -129
  8. src/utils.rs +33 -0
  9. tests/lib.rs +11 -23
Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rust-embed"
3
- version = "4.0.0"
3
+ version = "4.1.0"
4
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
5
  readme = "readme.md"
6
6
  documentation = "https://docs.rs/rust-embed"
@@ -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.1.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 }
appveyor.yml CHANGED
@@ -6,6 +6,12 @@
6
6
  # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets.
7
7
  os: Visual Studio 2015
8
8
 
9
+
10
+ # before repo cloning
11
+ init:
12
+ - git config --global core.symlinks true
13
+
14
+
9
15
  ## Build Matrix ##
10
16
 
11
17
  # This configuration will setup a build for each channel & target combination (12 windows
impl/Cargo.toml ADDED
@@ -0,0 +1,19 @@
1
+ [package]
2
+ name = "rust-embed-impl"
3
+ version = "4.1.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,137 @@
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
+ use std::env::current_dir;
19
+ // resolve relative to current path
20
+ let folder_path = utils::path_to_str(current_dir().unwrap().join(folder_path));
21
+ quote!{
22
+ impl #ident {
23
+ pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>> {
24
+ use std::fs::File;
25
+ use std::io::Read;
26
+ use std::path::Path;
27
+
28
+ let file_path = Path::new(#folder_path).join(file_path);
29
+ let mut file = match File::open(file_path) {
30
+ Ok(mut file) => file,
31
+ Err(_e) => {
32
+ return None
33
+ }
34
+ };
35
+ let mut data: Vec<u8> = Vec::new();
36
+ match file.read_to_end(&mut data) {
37
+ Ok(_) => Some(data),
38
+ Err(_e) => {
39
+ return None
40
+ }
41
+ }
42
+ }
43
+
44
+ pub fn iter() -> impl Iterator<Item = impl AsRef<str>> {
45
+ use std::path::Path;
46
+ use rust_embed::utils::get_files;
47
+ get_files(String::from(#folder_path)).map(|e| e.rel_path)
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ #[cfg(any(not(debug_assertions), feature = "debug-embed"))]
54
+ fn generate_assets(ident: &syn::Ident, folder_path: String) -> quote::Tokens {
55
+ use utils::{get_files, FileEntry};
56
+
57
+ let mut match_values = Vec::<Tokens>::new();
58
+ let mut list_values = Vec::<String>::new();
59
+
60
+ for FileEntry { rel_path, full_canonical_path } in get_files(folder_path) {
61
+ match_values.push(quote!{
62
+ #rel_path => Some(&include_bytes!(#full_canonical_path)[..]),
63
+ });
64
+ list_values.push(rel_path);
65
+ }
66
+
67
+ let array_len = list_values.len();
68
+
69
+ quote!{
70
+ impl #ident {
71
+ pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>> {
72
+ match file_path {
73
+ #(#match_values)*
74
+ _ => None,
75
+ }
76
+ }
77
+
78
+ pub fn iter() -> impl Iterator<Item = impl AsRef<str>> {
79
+ static items: [&str; #array_len] = [#(#list_values),*];
80
+ items.iter()
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ fn help() {
87
+ panic!("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
88
+ }
89
+
90
+ fn impl_rust_embed(ast: &syn::DeriveInput) -> Tokens {
91
+ match ast.body {
92
+ Body::Enum(_) => help(),
93
+ Body::Struct(ref data) => match data {
94
+ &VariantData::Struct(_) => help(),
95
+ _ => {}
96
+ },
97
+ };
98
+ let ident = &ast.ident;
99
+ if ast.attrs.len() == 0 || ast.attrs.len() > 1 {
100
+ help();
101
+ }
102
+ let value = &ast.attrs[0].value;
103
+ let literal_value = match value {
104
+ &MetaItem::NameValue(ref attr_name, ref value) => {
105
+ if attr_name == "folder" {
106
+ value
107
+ } else {
108
+ panic!("#[derive(RustEmbed)] attribute name must be folder");
109
+ }
110
+ }
111
+ _ => {
112
+ panic!("#[derive(RustEmbed)] attribute name must be folder");
113
+ }
114
+ };
115
+ let folder_path = match literal_value {
116
+ &Lit::Str(ref val, _) => val.clone(),
117
+ _ => {
118
+ panic!("#[derive(RustEmbed)] attribute value must be a string literal");
119
+ }
120
+ };
121
+ if !Path::new(&folder_path).exists() {
122
+ panic!(
123
+ "#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
124
+ folder_path,
125
+ std::env::current_dir().unwrap().to_str().unwrap()
126
+ );
127
+ };
128
+ generate_assets(ident, folder_path)
129
+ }
130
+
131
+ #[proc_macro_derive(RustEmbed, attributes(folder))]
132
+ pub fn derive_input_object(input: TokenStream) -> TokenStream {
133
+ let s = input.to_string();
134
+ let ast = syn::parse_derive_input(&s).unwrap();
135
+ let gen = impl_rust_embed(&ast);
136
+ gen.parse().unwrap()
137
+ }
impl/src/utils.rs ADDED
@@ -0,0 +1 @@
1
+ ../../src/utils.rs
readme.md CHANGED
@@ -13,21 +13,63 @@ You can use this to embed your css, js and images into a single executable which
13
13
 
14
14
  ```
15
15
  [dependencies]
16
- rust-embed="4.0.0"
16
+ rust-embed="4.1.0"
17
17
  ```
18
18
 
19
19
  ## Documentation
20
+
20
21
  You need to add the custom derive macro RustEmbed to your struct with an attribute `folder` which is the path to your static folder.
22
+
23
+ The path resolution works as follows:
24
+
25
+ - In `debug` and when `debug-embed` feature is not enabled, the folder path is resolved relative to where the binary is run from.
26
+ - In `release` or when `debug-embed` feature is enabled, the folder path is resolved relative to where `Cargo.toml` is.
27
+
21
28
  ```rust
22
29
  #[derive(RustEmbed)]
23
30
  #[folder = "examples/public/"]
24
31
  struct Asset;
25
32
  ```
26
- This macro adds a single static method `get` to your type. This method allows you to get your assets from the fs during dev and from the binary during release. It takes the file path as string and returns an `Option` with the bytes.
33
+
34
+
35
+ The macro will generate the following code:
36
+
27
37
  ```rust
38
+ impl Asset {
28
- pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>>
39
+ pub fn get(file_path: &str) -> Option<impl AsRef<[u8]>> {
40
+ ...
41
+ }
42
+
43
+ pub fn iter() -> impl Iterator<Item = impl AsRef<str>> {
44
+ ...
45
+ }
46
+ }
29
47
  ```
30
48
 
49
+ ### `get(file_path: &str)`
50
+
51
+ Given a relative path from the assets folder returns the bytes if found.
52
+
53
+ If the feature `debug-embed` is enabled or the binary compiled in release mode the bytes have been embeded in the binary and a `&'static [u8]` is returned.
54
+
55
+ Otherwise the bytes are read from the file system on each call and a `Vec<u8>` is returned.
56
+
57
+
58
+ ### `iter()`
59
+
60
+ Iterates the files in this assets folder.
61
+
62
+ If the feature `debug-embed` is enabled or the binary compiled in release mode a static array to the list of relative paths to the files is returned.
63
+
64
+ Otherwise the files are listed from the file system on each call.
65
+
66
+ ## Features
67
+
68
+ ### `debug-embed`
69
+
70
+ Always embed the files in the binary, even in debug mode.
71
+
72
+
31
73
  ## Usage
32
74
  ```rust
33
75
  #[macro_use]
@@ -40,6 +82,10 @@ struct Asset;
40
82
  fn main() {
41
83
  let index_html = Asset::get("index.html").unwrap();
42
84
  println!("{:?}", std::str::from_utf8(index_html.as_ref()));
85
+
86
+ for file in Asset::iter() {
87
+ println!("{}", file.as_ref());
88
+ }
43
89
  }
44
90
  ```
45
91
 
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,33 @@
1
+ use std;
2
+
3
+ #[cfg_attr(all(debug_assertions, not(feature = "debug-embed")), allow(unused))]
4
+ pub struct FileEntry {
5
+ pub rel_path: String,
6
+ pub full_canonical_path: String,
7
+ }
8
+
9
+ #[cfg_attr(all(debug_assertions, not(feature = "debug-embed")), allow(unused))]
10
+ pub fn get_files(folder_path: String) -> impl Iterator<Item = FileEntry> {
11
+ use std;
12
+ use walkdir;
13
+ walkdir::WalkDir::new(&folder_path)
14
+ .into_iter()
15
+ .filter_map(|e| e.ok())
16
+ .filter(|e| e.file_type().is_file())
17
+ .map(move |e| {
18
+ let rel_path = path_to_str(e.path().strip_prefix(&folder_path).unwrap());
19
+ let full_canonical_path = path_to_str(std::fs::canonicalize(e.path()).expect("Could not get canonical path"));
20
+
21
+ let rel_path = if std::path::MAIN_SEPARATOR == '\\' {
22
+ rel_path.replace('\\', "/")
23
+ } else {
24
+ rel_path
25
+ };
26
+
27
+ FileEntry { rel_path, full_canonical_path }
28
+ })
29
+ }
30
+
31
+ pub fn path_to_str<P: AsRef<std::path::Path>>(p: P) -> String {
32
+ p.as_ref().to_str().expect("Path does not have a string representation").to_owned()
33
+ }
tests/lib.rs CHANGED
@@ -1,13 +1,12 @@
1
1
  #[macro_use]
2
2
  extern crate rust_embed;
3
3
 
4
- #[test]
5
- #[cfg(debug_assertions)]
6
- fn dev() {
7
- #[derive(RustEmbed)]
4
+ #[derive(RustEmbed)]
8
- #[folder = "examples/public/"]
5
+ #[folder = "examples/public/"]
9
- struct Asset;
6
+ struct Asset;
10
7
 
8
+ #[test]
9
+ fn get_works() {
11
10
  match Asset::get("index.html") {
12
11
  None => assert!(false, "index.html should exist"),
13
12
  _ => assert!(true),
@@ -23,22 +22,11 @@ fn dev() {
23
22
  }
24
23
 
25
24
  #[test]
26
- #[cfg(not(debug_assertions))]
27
- fn prod() {
25
+ fn iter_works() {
26
+ let mut num_files = 0;
28
- #[derive(RustEmbed)]
27
+ for file in Asset::iter() {
29
- #[folder = "examples/public/"]
30
- struct Asset;
31
-
32
- match Asset::get("index.html") {
28
+ assert!(Asset::get(file.as_ref()).is_some());
33
- None => assert!(false, "index.html should exist"),
34
- _ => assert!(true),
29
+ num_files += 1;
35
- }
36
- match Asset::get("gg.html") {
37
- Some(_) => assert!(false, "gg.html should not exist"),
38
- _ => assert!(true),
39
- }
40
- match Asset::get("images/llama.png") {
41
- None => assert!(false, "llama.png should exist"),
42
- _ => assert!(true),
43
30
  }
31
+ assert_eq!(num_files, 6);
44
32
  }