bindata_impl/
lib.rs

1// Copyright 2018 Glassbear, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15
16extern 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(&current) {
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() ) // TODO:  Silently ignore errors?
110           .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}