rust-embed v8.13.0

#rust#proc-macro#http

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

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


89b83a8 — pyros2097 2018-03-18T22:02:46+05:30
add dev version of custom derive macro

Generated files not shown: Cargo.lock

Files changed (8) hide show
  1. .vscode/settings.json +82 -0
  2. Cargo.toml +7 -2
  3. examples/basic.rs +28 -0
  4. examples/rocket.rs +14 -14
  5. examples/rouille.rs +0 -1
  6. readme.md +20 -22
  7. src/lib.rs +134 -94
  8. tests/lib.rs +47 -0
.vscode/settings.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "editor.tabSize": 2,
3
+ "editor.fontSize": 15,
4
+ "editor.wordWrapColumn": 160,
5
+ "editor.minimap.enabled": false,
6
+ "search.exclude": {
7
+ "**/node_modules": true,
8
+ "**/bower_components": true,
9
+ "**/.git": true,
10
+ "**/.DS_Store": true,
11
+ "**/__pycache__": true,
12
+ "**/target": true,
13
+ "**/dist": true,
14
+ "**/.expo": true,
15
+ "**/.cache": true,
16
+ "**/.cache-loader": true,
17
+ },
18
+ "files.exclude": {
19
+ "**/node_modules": true,
20
+ "**/.git": true,
21
+ "**/.DS_Store": true,
22
+ "**/__pycache__": true,
23
+ "**/target": true,
24
+ "**/dist": true,
25
+ "**/.expo": true,
26
+ "**/.cache": true,
27
+ "**/.cache-loader": true,
28
+ },
29
+ "files.watcherExclude": {
30
+ "**/node_modules": true,
31
+ "**/dist": true,
32
+ "**/.git/objects/**": true,
33
+ "**/node_modules/**": true,
34
+ "**/__pycache__": true,
35
+ "**/target": true,
36
+ "**/.expo": true,
37
+ "**/.cache": true,
38
+ "**/.cache-loader": true,
39
+ },
40
+ "workbench.editor.showTabs": false,
41
+ "explorer.openEditors.visible": 0,
42
+ "gitProjectManager.baseProjectsFolders": [
43
+ "~/Code",
44
+ ],
45
+ "gitProjectManager.openInNewWindow": true,
46
+ "window.zoomLevel": 0,
47
+ "window.menuBarVisibility": "toggle",
48
+ "workbench.iconTheme": "vscode-icons",
49
+ "git.confirmSync": false,
50
+ "git.enableSmartCommit": true,
51
+ "workbench.startupEditor": "newUntitledFile",
52
+ "gitlens.advanced.messages": {
53
+ "suppressCommitHasNoPreviousCommitWarning": false,
54
+ "suppressCommitNotFoundWarning": false,
55
+ "suppressFileNotUnderSourceControlWarning": false,
56
+ "suppressGitVersionWarning": false,
57
+ "suppressLineUncommittedWarning": false,
58
+ "suppressNoRepositoryWarning": false,
59
+ "suppressResultsExplorerNotice": true,
60
+ "suppressUpdateNotice": true,
61
+ "suppressWelcomeNotice": false
62
+ },
63
+ "workbench.colorTheme": "Dracula Soft",
64
+ "git.autofetch": true,
65
+ "gitlens.keymap": "alternate",
66
+ "sqltools.format": {
67
+ "indentSize": 2
68
+ },
69
+ "sqltools.connections": [
70
+ {
71
+ "name": "Postgres",
72
+ "server": "localhost",
73
+ "dialect": "PostgreSQL",
74
+ "port": 5432,
75
+ "database": "postgres",
76
+ "username": "postgres",
77
+ "askForPassword": false,
78
+ "password": "postgres",
79
+ "connectionTimeout": 300
80
+ }
81
+ ]
82
+ }
Cargo.toml CHANGED
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "rust-embed"
3
- version = "0.5.2"
3
+ version = "1.0.0"
4
- description = "Rust Marco which loads files into the rust binary at compile time during release and loads the file from the fs during dev"
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"
7
7
  repository = "https://github.com/pyros2097/rust-embed"
@@ -10,7 +10,12 @@ keywords = ["http", "rocket", "static", "web", "server"]
10
10
  categories = ["web-programming::http-server"]
11
11
  authors = ["pyros2097 <[email protected]>"]
12
12
 
13
+ [lib]
14
+ proc-macro = true
15
+
13
16
  [dependencies]
17
+ syn = "0.11"
18
+ quote = "0.3"
14
19
  log = "0.4"
15
20
  walkdir = "2.1.4"
16
21
 
examples/basic.rs ADDED
@@ -0,0 +1,28 @@
1
+ #![feature(attr_literals)]
2
+ #[macro_use]
3
+ extern crate log;
4
+ #[macro_use]
5
+ extern crate rust_embed;
6
+ extern crate fern;
7
+
8
+ #[derive(RustEmbed)]
9
+ #[folder("examples/public/")]
10
+ struct Asset;
11
+
12
+ fn main() {
13
+ fern::Dispatch::new()
14
+ .format(move |out, message, record| {
15
+ out.finish(format_args!(
16
+ "[{}][{}] {}",
17
+ record.target(),
18
+ record.level(),
19
+ message
20
+ ))
21
+ })
22
+ .level(log::LevelFilter::Info)
23
+ .chain(std::io::stdout())
24
+ .apply()
25
+ .expect("Could not initialize logger");
26
+ let index_html = Asset::get("index.html").unwrap();
27
+ println!("{:?}", std::str::from_utf8(&index_html));
28
+ }
examples/rocket.rs CHANGED
@@ -1,22 +1,26 @@
1
- #![feature(test, plugin, decl_macro)]
1
+ #![feature(test, plugin, decl_macro, attr_literals)]
2
2
  #![plugin(rocket_codegen)]
3
+ extern crate fern;
4
+ #[macro_use]
5
+ extern crate log;
3
6
  extern crate rocket;
4
7
  extern crate rocket_contrib;
8
+ #[macro_use]
5
9
  extern crate rust_embed;
6
- extern crate fern;
7
- extern crate log;
8
10
 
9
11
  use std::path::PathBuf;
10
12
  use std::ffi::OsStr;
11
13
  use std::io::Cursor;
12
14
  use rocket::response;
13
15
  use rocket::http::{ContentType, Status};
16
+
17
+ #[derive(RustEmbed)]
18
+ #[folder("examples/public/")]
14
- use rocket::State;
19
+ struct Asset;
15
- use rust_embed::*;
16
20
 
17
21
  #[get("/")]
18
- fn index<'r>(asset: State<Asset>) -> response::Result<'r> {
22
+ fn index<'r>() -> response::Result<'r> {
19
- asset("index.html".to_owned()).map_or_else(
23
+ Asset::get("index.html").map_or_else(
20
24
  || Err(Status::NotFound),
21
25
  |d| {
22
26
  response::Response::build()
@@ -28,7 +32,7 @@ fn index<'r>(asset: State<Asset>) -> response::Result<'r> {
28
32
  }
29
33
 
30
34
  #[get("/dist/<file..>")]
31
- fn dist<'r>(asset: State<Asset>, file: PathBuf) -> response::Result<'r> {
35
+ fn dist<'r>(file: PathBuf) -> response::Result<'r> {
32
36
  let filename = file.display().to_string();
33
37
  let ext = file
34
38
  .as_path()
@@ -36,7 +40,7 @@ fn dist<'r>(asset: State<Asset>, file: PathBuf) -> response::Result<'r> {
36
40
  .and_then(OsStr::to_str)
37
41
  .expect("Could not get file extension");
38
42
  let content_type = ContentType::from_extension(ext).expect("Could not get file content type");
39
- asset(filename.clone()).map_or_else(
43
+ Asset::get(&filename.clone()).map_or_else(
40
44
  || Err(Status::NotFound),
41
45
  |d| {
42
46
  response::Response::build()
@@ -61,9 +65,5 @@ fn main() {
61
65
  .chain(std::io::stdout())
62
66
  .apply()
63
67
  .expect("Could not initialize logger");
64
- let asset = embed!("examples/public/".to_owned());
65
- rocket::ignite()
66
- .manage(asset)
67
- .mount("/", routes![index, dist])
68
+ rocket::ignite().mount("/", routes![index, dist]).launch();
68
- .launch();
69
69
  }
examples/rouille.rs CHANGED
@@ -1,4 +1,3 @@
1
- #[macro_use]
2
1
  extern crate rouille;
3
2
 
4
3
  use rouille::Response;
readme.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ## Rust Embed [![Build Status](https://travis-ci.org/pyros2097/rust-embed.svg?branch=master)](https://travis-ci.org/pyros2097/rust-embed) [![crates.io](http://meritbadge.herokuapp.com/rust-embed)](https://crates.io/crates/rust-embed)
2
- Rust Marco which loads files into the rust binary at compile time during release and loads the file from the fs during dev.
2
+ 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.
3
3
 
4
4
  You can use this to embed your css, js and images into a single executable.
5
5
 
@@ -7,55 +7,53 @@ This is similar to [go-embed](https://github.com/pyros2097/go-embed).
7
7
 
8
8
  This is similar to [pony-embed](https://github.com/pyros2097/pony-embed).
9
9
 
10
- Note:
11
-
12
- This is not the same as std macros,
13
- `include_bytes!`
14
- `include_str!`
15
- these are macros which generate code at compile time for only files.
16
- `embed!("examples/public/")` accepts folders and returns a function to get the contents using the file path
17
-
18
10
  ## Installation
19
11
 
20
12
  ```
21
13
  [dependencies]
22
- rust-embed="0.5.2"
14
+ rust-embed="1.0.0"
23
15
  ```
24
16
 
25
17
  ## Documentation
26
- The `embed!` macro takes a folder path and returns a function which allows you to get the file by passing the file path within the folder. So now you can statically compile all your assets i.e. your /static/ or /public/ folders into the rust executable and serve them during release and in development it will load the file from the file
27
- system so that it doesn't take to much time to compile;]
28
-
18
+ Declare a struct name it Asset or something and add an attribute `folder` to it which has the path to your static folder.
29
19
  ```rust
20
+ #![feature(attr_literals)]
21
+
22
+ #[derive(RustEmbed)]
30
- asset(path: String) -> Option<Vec<u8>>
23
+ #[folder("examples/public/")]
24
+ struct Asset;
31
25
  ```
32
26
 
33
27
  ## Usage
34
28
  ```rust
29
+ #![feature(attr_literals)]
35
30
  #[macro_use]
36
31
  extern crate rust_embed;
32
+ #[macro_use]
33
+ extern crate log;
37
34
 
35
+ #[derive(RustEmbed)]
36
+ #[folder("examples/public/")]
38
- use rust_embed::*;
37
+ struct Asset;
39
38
 
40
39
  fn main() {
41
- let asset = embed!("examples/public/".to_owned());
42
- let index_html = asset("index.html".to_owned()).unwrap();
40
+ let index_html = Asset::get("index.html").unwrap();
43
- println!("{}", index_html);
41
+ println!("{:?}", std::str::from_utf8(&index_html));
44
42
  }
45
43
  ```
46
44
 
47
45
  ## Examples
48
46
  To run the example in dev mode where it reads from the fs,
49
47
 
50
- `cargo run --example rocket`
48
+ `cargo run --example basic`
51
49
 
52
50
  To run the example in release mode where it reads from binary,
53
51
 
54
- `cargo run --release --example rocket`
52
+ `cargo run --release --example basic`
55
53
  ## Testing
56
- debug: `cargo test --lib`
54
+ debug: `cargo test --tests --lib`
57
55
 
58
- release: `cargo test --lib --release`
56
+ release: `cargo test --tests --lib --release`
59
57
 
60
58
  Go Rusketeers!
61
59
  The power is yours!
src/lib.rs CHANGED
@@ -1,110 +1,150 @@
1
+ #![recursion_limit = "1024"]
2
+ extern crate proc_macro;
1
3
  #[macro_use]
4
+ extern crate quote;
2
- extern crate log;
5
+ extern crate syn;
6
+
3
7
  extern crate walkdir;
4
8
 
5
- pub type Asset = Box<Fn(String) -> Option<Vec<u8>> + std::marker::Sync + std::marker::Send>;
9
+ use proc_macro::TokenStream;
10
+ use syn::*;
11
+ use quote::Tokens;
6
12
 
7
13
  #[cfg(debug_assertions)]
8
- pub fn generate_assets(parent_path: String) -> Asset {
14
+ fn generate_assets(ident: &syn::Ident, folder_path: String) -> quote::Tokens {
15
+ quote!{
9
- use std::fs::File;
16
+ use std::fs::File;
17
+ use std::io::Read;
10
- use std::path::Path;
18
+ use std::path::Path;
19
+
11
- use std::io::Read;
20
+ impl #ident {
12
- info!("loading folder -> {}", parent_path);
21
+ pub fn get(file_path: &str) -> Option<Vec<u8>> {
13
- Box::new(move |file_path| {
22
+ let folder_path = #folder_path;
14
- let name = &format!("{}{}", parent_path, file_path);
23
+ let name = &format!("{}{}", folder_path, file_path);
15
- let path = &Path::new(name);
24
+ let path = &Path::new(name);
16
- let key = String::from(path.to_str().expect("Path does not have a string representation"));
25
+ let key = String::from(path.to_str().expect("Path does not have a string representation"));
17
- info!("asset from file -> {}", key);
26
+ info!("file: {}", key);
18
- let mut file = match File::open(path) {
27
+ let mut file = match File::open(path) {
19
- Ok(mut file) => file,
28
+ Ok(mut file) => file,
20
- Err(e) => {
29
+ Err(e) => {
21
- error!("could not open file -> {} {}", key, e);
30
+ error!("file: {} {}", key, e);
22
- return None
31
+ return None
23
- }
32
+ }
24
- };
33
+ };
25
- let mut data: Vec<u8> = Vec::new();
34
+ let mut data: Vec<u8> = Vec::new();
26
- match file.read_to_end(&mut data) {
35
+ match file.read_to_end(&mut data) {
27
- Ok(_) => Some(data),
36
+ Ok(_) => Some(data),
28
- Err(e) => {
37
+ Err(e) => {
29
- error!("could not open file -> {} {}", key, e);
38
+ error!("file: {} {}", key, e);
30
- return None
39
+ return None
31
- }
40
+ }
32
- }
41
+ }
33
- })
42
+ }
43
+ }
44
+ }
34
45
  }
35
46
 
36
47
  #[cfg(not(debug_assertions))]
37
- pub fn generate_assets<'a>(parent_path: String) -> Asset {
48
+ pub fn generate_assets(folder_path: String) -> Asset {
38
- use std::fs::File;
49
+ use std::fs::File;
39
- use std::io::Read;
50
+ use std::io::Read;
40
- use std::path::Path;
51
+ use std::path::Path;
41
- use walkdir::WalkDir;
52
+ use walkdir::WalkDir;
42
- use std::collections::HashMap;
53
+ use std::collections::HashMap;
43
-
44
- info!("loading folder -> {}", parent_path);
54
+ info!("loading folder -> {}", parent_path);
45
- let mut map = HashMap::new();
55
+ let mut map = HashMap::new();
46
- for entry in WalkDir::new(parent_path.clone()).into_iter().filter_map(|e| e.ok()).filter(|e| e.file_type().is_file()) {
56
+ for entry in WalkDir::new(parent_path.clone())
57
+ .into_iter()
58
+ .filter_map(|e| e.ok())
59
+ .filter(|e| e.file_type().is_file())
60
+ {
47
- info!("asset from file -> {}", entry.path().display());
61
+ info!("asset from file -> {}", entry.path().display());
48
- let base = &parent_path.clone();
62
+ let base = &parent_path.clone();
63
+ let key = String::from(
64
+ entry
65
+ .path()
66
+ .to_str()
49
- let key = String::from(entry.path().to_str().expect("Path does not have a string representation")).replace(base, "");
67
+ .expect("Path does not have a string representation"),
68
+ ).replace(base, "");
50
- let mut file = File::open(&Path::new(&entry.path())).unwrap_or_else(|e| {
69
+ let mut file = File::open(&Path::new(&entry.path())).unwrap_or_else(|e| {
51
- panic!("could not open file -> {} {}", key, e);
70
+ panic!("could not open file -> {} {}", key, e);
52
- });
71
+ });
53
- let mut data: Vec<u8> = Vec::new();
72
+ let mut data: Vec<u8> = Vec::new();
54
- file.read_to_end(&mut data).unwrap_or_else(|e| {
73
+ file.read_to_end(&mut data).unwrap_or_else(|e| {
55
- panic!("could not read file -> {} {}", key, e);
74
+ panic!("could not read file -> {} {}", key, e);
56
- });
75
+ });
57
- map.insert(key, data);
76
+ map.insert(key, data);
77
+ }
78
+ Box::new(move |file_path| {
79
+ info!("asset from cache -> {}", file_path);
80
+ match map.get(&file_path) {
81
+ Some(s) => Some(s.to_vec()),
82
+ None => None,
58
83
  }
84
+ });
85
+ quote!{
59
- Box::new(move |file_path| {
86
+ impl #ident {
60
- info!("asset from cache -> {}", file_path);
87
+ pub fn get(file_path: &str) -> Option<Vec<u8>> {
61
- match map.get(&file_path) {
88
+ match file_path {
62
- Some(s) => Some(s.to_vec()),
89
+ "index.html" => Some(vec![]),
63
- None => None,
90
+ _ => None,
64
- }
91
+ }
65
- })
92
+ }
93
+ }
94
+ }
66
95
  }
67
96
 
68
- #[macro_export]
97
+ fn help() {
69
- macro_rules! embed {
98
+ panic!(
70
- ($x:expr) => ( $crate::generate_assets($x) )
99
+ "#[derive(RustEmbed)] should contain one attribute like this #[golem(\"examples/public/\")]"
100
+ );
71
101
  }
72
102
 
103
+ fn impl_rust_embed(ast: &syn::DeriveInput) -> Tokens {
73
- #[cfg(test)]
104
+ match ast.body {
105
+ Body::Enum(_) => help(),
106
+ Body::Struct(ref data) => match data {
107
+ &VariantData::Struct(_) => help(),
74
- mod tests {
108
+ _ => {}
75
- #[test]
109
+ },
110
+ };
76
- #[cfg(debug_assertions)]
111
+ let ident = &ast.ident;
77
- fn dev() {
78
- let asset = embed!("examples/public/".to_owned());
79
- match asset("index.html".to_owned()) {
112
+ if ast.attrs.len() == 0 || ast.attrs.len() > 1 {
80
- None => assert!(false, "index.html should exist"),
81
- _ => assert!(true),
113
+ help();
82
- }
114
+ }
115
+ let value = &ast.attrs[0].value;
116
+ let items = match value {
117
+ &MetaItem::List(ref attr_name, ref items) => {
83
- match asset("gg.html".to_owned()) {
118
+ if attr_name == "folder" {
119
+ items
120
+ } else {
84
- Some(_) => assert!(false, "gg.html should not exist"),
121
+ panic!("#[derive(RustEmbed)] attribute name must be folder");
85
- _ => assert!(true),
86
- }
122
+ }
87
- match asset("images/llama.png".to_owned()) {
88
- None => assert!(false, "llama.png should exist"),
89
- _ => assert!(true),
90
- }
91
123
  }
92
-
93
- #[test]
94
- #[cfg(not(debug_assertions))]
95
- fn prod() {
124
+ _ => {
96
- let asset = embed!("examples/public/".to_owned());
97
- match asset("index.html".to_owned()) {
98
- None => assert!(false, "index.html should exist"),
125
+ panic!("#[derive(RustEmbed)] attribute name must be folder");
99
- _ => assert!(true),
100
- }
101
- match asset("gg.html".to_owned()) {
102
- Some(_) => assert!(false, "gg.html should not exist"),
103
- _ => assert!(true),
104
- }
105
- match asset("images/llama.png".to_owned()) {
106
- None => assert!(false, "llama.png should exist"),
107
- _ => assert!(true),
108
- }
109
126
  }
127
+ };
128
+ let item = &items[0];
129
+ let lit = match item {
130
+ &NestedMetaItem::Literal(ref l) => l,
131
+ _ => {
132
+ panic!("Hello");
133
+ }
134
+ };
135
+ let folder_path = match lit {
136
+ &Lit::Str(ref val, _) => val.clone(),
137
+ _ => {
138
+ panic!("#[derive(RustEmbed)] attribute value must be a string literal");
139
+ }
140
+ };
141
+ generate_assets(ident, folder_path)
142
+ }
143
+
144
+ #[proc_macro_derive(RustEmbed, attributes(folder))]
145
+ pub fn derive_input_object(input: TokenStream) -> TokenStream {
146
+ let s = input.to_string();
147
+ let ast = syn::parse_derive_input(&s).unwrap();
148
+ let gen = impl_rust_embed(&ast);
149
+ gen.parse().unwrap()
110
150
  }
tests/lib.rs ADDED
@@ -0,0 +1,47 @@
1
+ #![feature(attr_literals)]
2
+ #[macro_use]
3
+ extern crate log;
4
+ #[macro_use]
5
+ extern crate rust_embed;
6
+
7
+ #[test]
8
+ #[cfg(debug_assertions)]
9
+ fn dev() {
10
+ #[derive(RustEmbed)]
11
+ #[folder("examples/public/")]
12
+ struct Asset;
13
+
14
+ match Asset::get("index.html") {
15
+ None => assert!(false, "index.html should exist"),
16
+ _ => assert!(true),
17
+ }
18
+ match Asset::get("gg.html") {
19
+ Some(_) => assert!(false, "gg.html should not exist"),
20
+ _ => assert!(true),
21
+ }
22
+ match Asset::get("images/llama.png") {
23
+ None => assert!(false, "llama.png should exist"),
24
+ _ => assert!(true),
25
+ }
26
+ }
27
+
28
+ #[test]
29
+ #[cfg(not(debug_assertions))]
30
+ fn prod() {
31
+ #[derive(RustEmbed)]
32
+ #[folder("examples/public/")]
33
+ struct Asset;
34
+
35
+ match Asset::get("index.html") {
36
+ None => assert!(false, "index.html should exist"),
37
+ _ => assert!(true),
38
+ }
39
+ match Asset::get("gg.html") {
40
+ Some(_) => assert!(false, "gg.html should not exist"),
41
+ _ => assert!(true),
42
+ }
43
+ match Asset::get("images/llama.png") {
44
+ None => assert!(false, "llama.png should exist"),
45
+ _ => assert!(true),
46
+ }
47
+ }