include-dir-macro 0.2.0

Provides a macro to include a directory tree of files in the compiled binary
Documentation
#![recursion_limit = "128"]
extern crate proc_macro;

#[macro_use]
extern crate quote;

use std::path::{Path, PathBuf};
use std::str;
use proc_macro::TokenStream;

use syn::{Lit, StrStyle, Token, TokenTree, parse_token_trees};


#[proc_macro]
pub fn include_dir(input: TokenStream) -> TokenStream {
    let foo = input.to_string();
    let args = parse_token_trees(&foo).unwrap();
    let gen = impl_include_dir(args).unwrap();
    gen.parse().unwrap()
}


fn get_files<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
    let mut files = vec![];
    let listing: Vec<_> = ::std::fs::read_dir(dir)
        .expect("could not read directory")
        .map(|entry| entry.unwrap().path())
        .collect();
    for path in listing {
        if path.is_file() {
            files.push(path)
        } else if path.is_dir() {
            for file in get_files(&path) {
                files.push(file.into())
            }
        }
    }
    files
}


fn path_to_str_literal<P: AsRef<Path>>(path: P) -> Token {
    Token::Literal(Lit::Str(
        path.as_ref().to_str().unwrap().to_owned(),
        StrStyle::Cooked,
    ))
}

fn get_path_from_args(args: Vec<TokenTree>) -> Result<PathBuf, &'static str> {
    match args.len() {
        0 => Err("empty"),
        1 => {
            let nexttree = args.into_iter().next().unwrap();
            match nexttree {
                TokenTree::Token(Token::Literal(Lit::Str(ref val, ..))) => Ok(val.into()),
                _ => Err("not str"),
            }
        }
        _ => Err("multiple trees"),
    }
}


fn impl_include_dir(args: Vec<TokenTree>) -> Result<quote::Tokens, &'static str> {
    let dir = get_path_from_args(args)?;
    let paths: Vec<_> = get_files(&dir);

    let keys: Vec<_> = paths
        .iter()
        .map(|path| path.strip_prefix(&dir).unwrap())
        .map(path_to_str_literal)
        .collect();

    let vals: Vec<_> = paths
        .iter()
        .map(|path| ::std::fs::canonicalize(path).expect("found"))
        .map(path_to_str_literal)
        .collect();

    Ok(quote! {
        {
            let mut __include_dir_hashmap = ::std::collections::HashMap::new();
            #( __include_dir_hashmap.insert(::std::path::Path::new(#keys), &include_bytes!(#vals)[..]); )*
            __include_dir_hashmap
        }
    })
}


#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}