use mlua::chunk;
use mlua::prelude::*;
use rust_embed::{EmbeddedFile, RustEmbed};
use std::str;
/// Embed everything that would otherwise be installed to datadir
#[derive(RustEmbed)]
#[folder = "."]
#[exclude = ".*"]
#[exclude = "*.a"]
#[exclude = "*.abnf"]
#[exclude = "*.ac"]
#[exclude = "*.am"]
#[exclude = "*.in"]
#[exclude = "*.json"]
#[exclude = "*.m4"]
#[exclude = "*.md"]
#[exclude = "*.nix"]
#[exclude = "*.so"]
#[exclude = "*.yml"]
#[exclude = "*~"]
#[exclude = "Makefile*"]
#[exclude = "bootstrap.sh"]
#[exclude = "config*"]
#[exclude = "libtool"]
#[exclude = "rust-toolchain"]
#[exclude = "sile*"]
#[exclude = "autom4te.cache/*"]
#[exclude = "build-aux/*"]
#[exclude = "cmake/*"]
#[exclude = "completions/*"]
#[exclude = "documentation/*"]
#[exclude = "justenough/*"]
#[exclude = "libtexpdf/*"]
#[exclude = "node_modules/*"]
#[exclude = "rusile/*"]
#[exclude = "rust-api-docs/*"]
#[exclude = "spec/*"]
#[exclude = "src/*"]
#[exclude = "target/*"]
#[exclude = "tests/*"]
// @EMBEDDED_INCLUDE_LIST@ -- this marker line gets replaced by a list of includes
pub struct SileModules;
// Link Lua loader functions from C modules that Lua would otherwise be loading externally that
// we've linked into the CLI binary. Linking happens in build-aux/build.rs.
extern "C-unwind" {
fn luaopen_fontmetrics(lua: *mut mlua::lua_State) -> i32;
fn luaopen_justenoughfontconfig(lua: *mut mlua::lua_State) -> i32;
fn luaopen_justenoughharfbuzz(lua: *mut mlua::lua_State) -> i32;
fn luaopen_justenoughicu(lua: *mut mlua::lua_State) -> i32;
fn luaopen_justenoughlibtexpdf(lua: *mut mlua::lua_State) -> i32;
fn luaopen_svg(lua: *mut mlua::lua_State) -> i32;
}
/// Register a Lua function in the loaders/searchers table to return C modules linked into the CLI
/// binary and another to return embedded Lua resources as Lua modules. See discussion in mlua:
/// https://github.com/khvzak/mlua/discussions/322
pub fn inject_embedded_loaders(lua: Lua) -> crate::Result<Lua> {
let package: LuaTable = lua.globals().get("package").unwrap();
let loaders: LuaTable = match package.get("loaders").unwrap() {
LuaValue::Table(loaders) => loaders,
LuaValue::Nil => package.get("searchers").unwrap(),
_ => panic!("Unable to find appropriate interface to inject embedded loader"),
};
let embedded_rusile_loader = lua
.create_function(|lua, module: String| match module.as_str() {
"rusile" => lua
.create_function(move |lua, _: ()| crate::get_rusile_exports(lua))
.map(LuaValue::Function),
_ => format!("Module '{module}' is embedded in Rust binary").into_lua(lua),
})
.unwrap();
loaders.push(embedded_rusile_loader).unwrap();
let embedded_ffi_loader = lua
.create_function(|lua, module: String| unsafe {
match module.as_str() {
"fontmetrics" => lua
.create_c_function(luaopen_fontmetrics)
.map(LuaValue::Function),
"justenoughfontconfig" => lua
.create_c_function(luaopen_justenoughfontconfig)
.map(LuaValue::Function),
"justenoughharfbuzz" => lua
.create_c_function(luaopen_justenoughharfbuzz)
.map(LuaValue::Function),
"justenoughicu" => lua
.create_c_function(luaopen_justenoughicu)
.map(LuaValue::Function),
"justenoughlibtexpdf" => lua
.create_c_function(luaopen_justenoughlibtexpdf)
.map(LuaValue::Function),
"svg" => lua.create_c_function(luaopen_svg).map(LuaValue::Function),
_ => format!("C Module '{module}' is not linked in Rust binary").into_lua(lua),
}
})
.unwrap();
loaders.push(embedded_ffi_loader).unwrap();
let embedded_lua_loader = lua
.create_function(|lua, module: String| {
let module_path = module.replace('.', "/");
let luaversion: LuaString = lua
.load(chunk! {
return _VERSION:match("%d+%.%d+")
})
.eval()
.unwrap();
let luaversion: &str = &luaversion.to_str().unwrap();
let mut package_epath: Vec<&str> = vec!["?/init.lua", "?.lua", "lua-libraries/?.lua"];
let path = format!("lua_modules/lib/lua/{}/?/init.lua", luaversion);
package_epath.push(&path);
let path = format!("lua_modules/lib/lua/{}/?.lua", luaversion);
package_epath.push(&path);
let path = format!("lua_modules/share/lua/{}/?/init.lua", luaversion);
package_epath.push(&path);
let path = format!("lua_modules/share/lua/{}/?.lua", luaversion);
package_epath.push(&path);
let mut resource_option: Option<EmbeddedFile> = None;
for pattern in &package_epath {
let path = pattern.replace('?', &module_path);
let embedded = SileModules::get(&path);
if embedded.is_some() {
resource_option = embedded;
break;
}
}
match resource_option {
Some(module) => lua
.create_function(move |lua, _: ()| {
let data = str::from_utf8(module.data.as_ref())
.expect("Embedded content is not valid UTF-8");
lua.load(data).call::<LuaValue>(())
})
.map(LuaValue::Function),
None => format!("Module '{module}' is not embedded in Rust binary").into_lua(lua),
}
})
.unwrap();
loaders.push(embedded_lua_loader).unwrap();
let embedded_ftl_loader = lua
.create_function(|lua, module: String| {
let module_path = module.replace('.', "/");
let pattern = "?.ftl";
let path = pattern.replace('?', &module_path);
match SileModules::get(&path) {
Some(module) => lua
.create_function(move |lua, _: ()| {
let data = str::from_utf8(module.data.as_ref())
.expect("Embedded content is not valid UTF-8");
lua.load(chunk! {
return assert(fluent:add_messages($data))
})
.call::<LuaValue>(())
})
.map(LuaValue::Function),
_ => format!("FTL resource '{module_path}' is not embedded in Rust binary")
.into_lua(lua),
}
})
.unwrap();
loaders.push(embedded_ftl_loader).unwrap();
Ok(lua)
}