include_lua/
lib.rs

1use std::collections::HashMap;
2
3use proc_macro_hack::proc_macro_hack;
4use rlua::{Result, Context, UserData, UserDataMethods, MetaMethod, Value, Table, RegistryKey};
5
6/// A macro that embeds a lua source tree on disk into the binary, similarly to how `include_str!`
7/// can include a single file. Called like `include_lua!("name": "path")`, where name is a label
8/// that appears in lua stacktraces involving code loaded from the tree, and path specifies a folder
9/// relative to `src/` in which the tree can be found. `name` defaults to `path` if omitted.
10#[proc_macro_hack]
11pub use include_lua_macro::include_lua;
12
13/// Represents a Lua source tree embedded into a binary via [`include_lua!`][include_lua].
14pub struct LuaModules {
15    files: HashMap<String, (String, String)>,
16    prefix: String,
17}
18
19impl LuaModules {
20    #[doc(hidden)] // This is not a public API!
21    pub fn __new(files: HashMap<String, (String, String)>, prefix: &str) -> LuaModules {
22        LuaModules { files: files, prefix: prefix.to_string() }
23    }
24}
25
26/// A piece of [`UserData`][UserData] that acts like a Lua searcher.
27/// When called as a function with a single string parameter, attempts to load
28/// (but not execute) a module by that name. If no module is found, returns nil.
29pub struct Searcher(LuaModules, RegistryKey);
30
31impl UserData for Searcher {
32     fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
33        methods.add_meta_method(MetaMethod::Call, |ctx, this, value: String| {
34            Ok(match this.0.files.get(&value) {
35                Some((source, path)) => {
36                    Value::Function(ctx.load(source)
37                        .set_name(&format!("{}\\{} (virtual)", &this.0.prefix, path))?
38                        .set_environment(ctx.registry_value::<Table>(&this.1)?)?
39                        .into_function()?
40                    )
41                }
42                None => Value::Nil,
43            })
44        });
45    }
46}
47
48/// An extension trait for [`Context`][Context] that allows the loading of [`LuaModules`][LuaModules] instances.
49pub trait ContextExt<'a> {
50    /// Makes the source tree represented by `modules` accessible to `require` calls within this context.
51    fn add_modules(&self, modules: LuaModules) -> Result<()>;
52
53    /// Makes the source tree represented by `modules` accessible to `require` calls within this context.
54    /// All modules loaded from the source tree will have their environment set to `environment`.
55    fn add_modules_with_env(&self, modules: LuaModules, environment: Table<'a>) -> Result<()>;
56
57    /// Creates a [`Searcher`][Searcher] instance from the given [`LuaModules`][LuaModules] instance.
58    fn make_searcher(&self, modules: LuaModules) -> Result<Searcher>;
59
60    /// Creates a [`Searcher`][Searcher] instance from the given [`LuaModules`][LuaModules] instance.
61    /// All modules loaded by the searcher will have their environment set to `environment`.
62    fn make_searcher_with_env(&self, modules: LuaModules, environment: Table<'a>) -> Result<Searcher>;
63}
64
65impl<'a> ContextExt<'a> for Context<'a> {
66    fn add_modules(&self, modules: LuaModules) -> Result<()> {
67        self.add_modules_with_env(modules, self.globals())
68    }
69
70    fn add_modules_with_env(&self, modules: LuaModules, environment: Table<'a>) -> Result<()> {
71        let searchers: Table = self.globals().get::<_, Table>("package")?.get("searchers")?;
72        searchers.set(searchers.len()? + 1, self.make_searcher_with_env(modules, environment)?)
73    }
74
75    fn make_searcher(&self, modules: LuaModules) -> Result<Searcher> {
76        self.make_searcher_with_env(modules, self.globals())
77    }
78
79    fn make_searcher_with_env(&self, modules: LuaModules, environment: Table<'a>) -> Result<Searcher> {
80        Ok(Searcher(modules, self.create_registry_value(environment)?))
81    }
82}