1#![recursion_limit = "128"]
2extern crate proc_macro;
3
4#[macro_use]
5extern crate quote;
6
7use std::path::{Path, PathBuf};
8use std::str;
9use proc_macro::TokenStream;
10
11use syn::{Lit, StrStyle, Token, TokenTree, parse_token_trees};
12
13
14#[proc_macro]
15pub fn include_dir(input: TokenStream) -> TokenStream {
16 let foo = input.to_string();
17 let args = parse_token_trees(&foo).unwrap();
18 let gen = impl_include_dir(args).unwrap();
19 gen.parse().unwrap()
20}
21
22
23fn get_files<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
24 let mut files = vec![];
25 let listing: Vec<_> = ::std::fs::read_dir(dir)
26 .expect("could not read directory")
27 .map(|entry| entry.unwrap().path())
28 .collect();
29 for path in listing {
30 if path.is_file() {
31 files.push(path)
32 } else if path.is_dir() {
33 for file in get_files(&path) {
34 files.push(file.into())
35 }
36 }
37 }
38 files
39}
40
41
42fn path_to_str_literal<P: AsRef<Path>>(path: P) -> Token {
43 Token::Literal(Lit::Str(
44 path.as_ref().to_str().unwrap().to_owned(),
45 StrStyle::Cooked,
46 ))
47}
48
49fn get_path_from_args(args: Vec<TokenTree>) -> Result<PathBuf, &'static str> {
50 match args.len() {
51 0 => Err("empty"),
52 1 => {
53 let nexttree = args.into_iter().next().unwrap();
54 match nexttree {
55 TokenTree::Token(Token::Literal(Lit::Str(ref val, ..))) => Ok(val.into()),
56 _ => Err("not str"),
57 }
58 }
59 _ => Err("multiple trees"),
60 }
61}
62
63
64fn impl_include_dir(args: Vec<TokenTree>) -> Result<quote::Tokens, &'static str> {
65 let dir = get_path_from_args(args)?;
66 let paths: Vec<_> = get_files(&dir);
67
68 let keys: Vec<_> = paths
69 .iter()
70 .map(|path| path.strip_prefix(&dir).unwrap())
71 .map(path_to_str_literal)
72 .collect();
73
74 let vals: Vec<_> = paths
75 .iter()
76 .map(|path| ::std::fs::canonicalize(path).expect("found"))
77 .map(path_to_str_literal)
78 .collect();
79
80 Ok(quote! {
81 {
82 let mut __include_dir_hashmap = ::std::collections::HashMap::new();
83 #( __include_dir_hashmap.insert(::std::path::Path::new(#keys), &include_bytes!(#vals)[..]); )*
84 __include_dir_hashmap
85 }
86 })
87}
88
89
90#[cfg(test)]
91mod tests {
92 #[test]
93 fn it_works() {
94 assert_eq!(2 + 2, 4);
95 }
96}