1extern crate proc_macro;
17extern crate proc_macro2;
18#[macro_use] extern crate quote;
19extern crate syn;
20extern crate walkdir;
21
22use std::env;
23use std::fs::File;
24use std::io::BufReader;
25use std::io::prelude::*;
26#[allow(unused)]
27use std::path::{Path, PathBuf};
28
29use proc_macro::TokenStream;
30use proc_macro2::{TokenTree, TokenNode, Delimiter, Span};
31use syn::{Lit, LitStr, Meta, MetaNameValue, NestedMeta, MetaList };
32use quote::{Tokens, ToTokens};
33
34
35struct Asset(String, Bytes);
36impl ToTokens for Asset {
37 fn to_tokens(&self, tokens: &mut Tokens) {
38 let &Asset(ref path, ref contents) = self;
39 let asset_tokens = quote!{ #path => Some(vec!#contents), }.into();
40
41 tokens.append(TokenTree {
42 span: Span::def_site(),
43 kind: TokenNode::Group(
44 Delimiter::None,
45 asset_tokens,
46 ),
47 })
48 }
49}
50
51
52struct Bytes(Vec<u8>);
53impl ToTokens for Bytes {
54 fn to_tokens(&self, tokens: &mut Tokens) {
55 let bytes = &self.0;
56 let byte_tokens = quote!{ #(#bytes),* }.into();
57
58 tokens.append(TokenTree {
59 span: Span::def_site(),
60 kind: TokenNode::Group(
61 Delimiter::Bracket,
62 byte_tokens,
63 ),
64 });
65 }
66}
67
68
69#[allow(unused)]
70fn read_bytes(filename: &Path) -> Asset {
71 match File::open(filename) {
72 Ok(file) => {
73 let mut reader = BufReader::new(file);
74 let mut contents = Vec::new();
75 let _ = reader.read_to_end(&mut contents);
76
77 Asset(filename.to_str().unwrap().to_owned(), Bytes(contents))
78 },
79 Err(error) => {
80 panic!(format!("could not open {:?}: {}", filename, error));
81 }
82 }
83}
84
85fn abs_path(path: String) -> PathBuf {
86 let current = env::current_dir().unwrap();
87 let mut path: PathBuf = path.into();
88 if path.is_relative() {
89 path = current.join(path)
90 }
91
92 path
93}
94
95fn relativize_path(path: PathBuf) -> String {
96 let current = env::current_dir().unwrap();
97 let path = match path.strip_prefix(¤t) {
98 Ok(path) => path,
99 Err(_) => &path,
100 };
101
102 path.to_str().unwrap().to_string()
103}
104
105
106fn walk(path: PathBuf) -> Vec<Asset> {
107 let entries = walkdir::WalkDir::new(path.clone()).into_iter();
108
109 entries.filter_map(|e| e.ok() ) .filter(|e| e.file_type().is_file())
111 .map(|f| read_bytes(f.path()))
112 .map(|Asset(path, contents)| Asset(relativize_path(path.into()), contents))
113 .collect()
114}
115
116#[proc_macro_derive(BinDataImpl, attributes(BinDataImplContent))]
117pub fn bindata_impl(input: TokenStream) -> TokenStream {
118 fn parse_meta_list(m: Meta) -> Option<MetaList> {
119 match m {
120 Meta::List(meta) => Some(meta),
121 _ => None,
122 }
123 }
124
125 fn parse_meta_namevalue(m: Meta) -> Option<MetaNameValue> {
126 match m {
127 Meta::NameValue(namevalue) => Some(namevalue),
128 _ => None,
129 }
130 }
131
132 fn parse_nestedmeta_meta(m: NestedMeta) -> Option<Meta> {
133 match m {
134 NestedMeta::Meta(meta) => Some(meta),
135 _ => None
136 }
137 }
138
139 fn parse_string_literal(l: Lit) -> Option<LitStr> {
140 match l {
141 Lit::Str(string) => Some(string),
142 _ => None,
143 }
144 }
145
146 let input: syn::DeriveInput = syn::parse(input).unwrap();
147 let ident = input.ident;
148 let values: Vec<Asset> = input.attrs.iter()
149 .filter_map(|a| a.interpret_meta())
150 .filter(|m| m.name() == "BinDataImplContent")
151 .filter_map(parse_meta_list)
152 .flat_map(|bindata|
153 bindata.nested.into_iter()
154 .filter_map(parse_nestedmeta_meta)
155 .filter_map(parse_meta_namevalue)
156 .map(|nv| nv.lit)
157 .filter_map(parse_string_literal))
158 .map(|l| l.value())
159 .flat_map(|path| walk(abs_path(path)))
160 .collect();
161 let expanded = quote! {
162 impl #ident {
163 fn get(&self, key: &str) -> Option<Vec<u8>> {
164 match key {
165 #(#values)*
166 _ => None,
167 }
168 }
169 }
170 };
171
172 expanded.into()
173}