include_dir_macro/
lib.rs

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}