include_lua_macro/
lib.rs

1extern crate proc_macro;
2
3use std::{env, path::{self, PathBuf}};
4use proc_macro_hack::proc_macro_hack;
5use quote::quote;
6use syn::{
7    parse_macro_input, Result, LitStr, Token,
8    parse::{Parse, ParseStream}, 
9    export::{Span, TokenStream2 as TokenStream}
10};
11use walkdir::WalkDir;
12
13
14#[proc_macro_hack]
15pub fn include_lua(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
16    parse_macro_input!(input as IncludeLua).expand().into()
17}
18
19struct IncludeLua(LitStr, LitStr);
20
21impl IncludeLua {
22    fn expand(self) -> TokenStream {
23        let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").expect("Could not locate active Cargo.toml!").into();
24        let lua_dir = manifest_dir.join("src").join(self.0.value());
25        let modules = WalkDir::new(&lua_dir).into_iter().filter_map(|entry| {
26            match entry {
27                Ok(ref entry) if entry.file_type().is_file() => {
28                    let path = entry.path().strip_prefix(&lua_dir).expect("Reached file outside of lua directory???");
29                    if path.extension() == Some("lua".as_ref()) {
30                        let module = if path.parent().is_some() && path.file_stem().expect("Missing file name!") == &"init".as_ref() {
31                            path.parent().unwrap().to_str().map(|s| s.replace(path::MAIN_SEPARATOR, "."))
32                        } 
33                        else {
34                            // Do paths with a different separator show up? If so, fix this.
35                            let mut s = path.to_str().map(|s| s.replace(path::MAIN_SEPARATOR, "."));
36                            s.as_mut().map(|s| s.truncate(s.len() - 4));
37                            s
38                        };
39                        return module.map(|module| (module, path.to_owned()))
40                    }
41                    None
42                }
43                Err(e) => panic!("An error occured while searching for lua modules: {}", e),
44                _ => None,
45            }
46        });
47
48        let add_files = modules.map(|(module, path)| {
49            let module = LitStr::new(&module, Span::call_site());
50            let real_path = LitStr::new(&PathBuf::from(self.0.value()).join(&path).to_string_lossy(), Span::call_site());
51            let virtual_path = LitStr::new(&path.to_string_lossy(), Span::call_site());
52            quote! {
53                files.insert(#module.to_string(), (include_str!(#real_path).to_string(), #virtual_path.to_string()))
54            }
55        });
56
57        let name = &self.1;
58        quote! { {
59            #[allow(unknown_lints)]
60            #[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))]
61            #[allow(rust_2018_idioms)]
62            extern crate include_lua as _include_lua;
63
64            let mut files = ::std::collections::HashMap::<String, (String, String)>::new();
65            #(#add_files;)*
66            _include_lua::LuaModules::__new(files, #name)
67        } }
68    }
69}
70
71impl Parse for IncludeLua {
72    fn parse(input: ParseStream) -> Result<Self> {
73        let (path_str, name) = {
74            let s1: LitStr = input.parse()?;
75            match input.parse::<Token![:]>() {
76                Ok(_) => (input.parse()?, s1),
77                Err(_) => (s1.clone(), s1),
78            }
79        };
80        if !input.is_empty() { return Err(input.error("Unknown token in include_lua invocation!")) }
81        Ok(IncludeLua(path_str, name))
82    }
83}