Use template engine to build mod_data.lua

The string-building version became too complex to maintain properly.
This commit is contained in:
Lucas Schwiderski 2023-11-12 23:20:10 +01:00 committed by Lucas Schwiderski
parent 15498cc2e0
commit 13e77a2097
5 changed files with 99 additions and 68 deletions

10
Cargo.lock generated
View file

@ -906,6 +906,7 @@ dependencies = [
"futures", "futures",
"lazy_static", "lazy_static",
"luajit2-sys", "luajit2-sys",
"minijinja",
"nexusmods", "nexusmods",
"oodle", "oodle",
"path-slash", "path-slash",
@ -2084,6 +2085,15 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minijinja"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "208758577ef2c86cf5dd3e85730d161413ec3284e2d73b2ef65d9a24d9971bcb"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"

View file

@ -35,3 +35,4 @@ ansi-parser = "0.9.0"
string_template = "0.2.1" string_template = "0.2.1"
luajit2-sys = { path = "../../lib/luajit2-sys", version = "*" } luajit2-sys = { path = "../../lib/luajit2-sys", version = "*" }
async-recursion = "1.0.5" async-recursion = "1.0.5"
minijinja = "1.0.10"

View file

@ -0,0 +1,27 @@
return {
{% for mod in mods %}
{
id = "{{ mod.id }}",
name = "{{ mod.name }}",
bundled = {{ mod.bundled }},
packages = {
{% for pkg in mod.packages %}
"{{ pkg }}",
{% endfor %}
},
run = function()
{% if mod.data is none %}
return dofile("{{ mod.init }}")
{% else %}
new_mod("{{ mod.id }}", {
mod_script = "{{ mod.init }}",
mod_data = "{{ mod.data }}",
{% if not mod.localization is none %}
mod_localization = "{{ mod.localization }}",
{% endif %}
})
{% endif %}
end,
},
{% endfor %}
}

View file

@ -61,14 +61,14 @@ local function patch_mod_loading_state()
if state == "load_package" and package_manager:update() then if state == "load_package" and package_manager:update() then
log("StateBootLoadMods", "Packages loaded, loading mods") log("StateBootLoadMods", "Packages loaded, loading mods")
self._state = "load_mods" self._state = "load_mods"
local ModLoader = require("scripts/mods/dml/init") local DML = require("scripts/mods/dml/init")
local mod_data = require("scripts/mods/mod_data") local mod_data = require("scripts/mods/mod_data")
local mod_loader = ModLoader:new(mod_data, self._parent:gui()) local mod_loader = DML.create_loader(mod_data, self._parent:gui())
self._mod_loader = mod_loader self._dml = DML
Managers.mod = mod_loader Managers.mod = mod_loader
elseif state == "load_mods" and self._mod_loader:update(dt) then elseif state == "load_mods" and self._dml.update(Managers.mod, dt) then
log("StateBootLoadMods", "Mods loaded, exiting") log("StateBootLoadMods", "Mods loaded, exiting")
return true, false return true, false
end end

View file

@ -8,7 +8,7 @@ use color_eyre::eyre::Context;
use color_eyre::{eyre, Help, Report, Result}; use color_eyre::{eyre, Help, Report, Result};
use futures::StreamExt; use futures::StreamExt;
use futures::{stream, TryStreamExt}; use futures::{stream, TryStreamExt};
use path_slash::PathBufExt; use minijinja::Environment;
use sdk::filetype::lua; use sdk::filetype::lua;
use sdk::filetype::package::Package; use sdk::filetype::package::Package;
use sdk::murmur::Murmur64; use sdk::murmur::Murmur64;
@ -201,10 +201,10 @@ async fn copy_recursive(
async move { async move {
if is_dir { if is_dir {
tracing::trace!("Creating directory '{}'", dest.display()); tracing::trace!("Creating directory '{}'", dest.display());
fs::create_dir(&dest) // Instead of trying to filter "already exists" errors out explicitly,
.await // we just ignore all. It'll fail eventually with the next copy operation.
.map(|_| ()) let _ = fs::create_dir(&dest).await;
.wrap_err_with(|| format!("Failed to create directory '{}'", dest.display())) Ok(())
} else { } else {
tracing::trace!("Copying file '{}' -> '{}'", path.display(), dest.display()); tracing::trace!("Copying file '{}' -> '{}'", path.display(), dest.display());
fs::copy(&path, &dest).await.map(|_| ()).wrap_err_with(|| { fs::copy(&path, &dest).await.map(|_| ()).wrap_err_with(|| {
@ -262,67 +262,60 @@ async fn copy_mod_folders(state: Arc<ActionState>) -> Result<Vec<String>> {
Ok(ids) Ok(ids)
} }
fn build_mod_data_lua(state: Arc<ActionState>) -> String { fn build_mod_data_lua(state: Arc<ActionState>) -> Result<String> {
let mut lua = String::from("return {\n"); #[derive(Serialize)]
struct TemplateDataMod {
// DMF is handled explicitely by the loading procedures, as it actually drives most of that id: String,
// and should therefore not show up in the load order. name: String,
for mod_info in state.mods.iter().filter(|m| m.id != "dml" && m.enabled) { bundled: bool,
lua.push_str(" {\n name = \""); init: String,
lua.push_str(&mod_info.name); data: Option<String>,
localization: Option<String>,
lua.push_str("\",\n id = \""); packages: Vec<String>,
lua.push_str(&mod_info.id);
lua.push_str("\",\n bundled = \"");
if mod_info.bundled {
lua.push_str("true");
} else {
lua.push_str("false");
} }
lua.push_str("\",\n run = function()\n"); let mut env = Environment::new();
env.add_template("mod_data.lua", include_str!("../../assets/mod_data.lua.j2"))
.wrap_err("Failed to compile template for `mod_data.lua`")?;
let tmpl = env
.get_template("mod_data.lua")
.wrap_err("Failed to get template `mod_data.lua`")?;
let resources = &mod_info.resources; let data: Vec<TemplateDataMod> = state
if resources.data.is_some() || resources.localization.is_some() { .mods
lua.push_str(" new_mod(\""); .iter()
lua.push_str(&mod_info.id); .filter_map(|m| {
lua.push_str("\", {\n mod_script = \""); if m.id == "dml" || !m.enabled {
lua.push_str(&resources.init.to_slash_lossy()); return None;
if let Some(data) = resources.data.as_ref() {
lua.push_str("\",\n mod_data = \"");
lua.push_str(&data.to_slash_lossy());
} }
if let Some(localization) = &resources.localization { Some(TemplateDataMod {
lua.push_str("\",\n mod_localization = \""); id: m.id.clone(),
lua.push_str(&localization.to_slash_lossy()); name: m.name.clone(),
} bundled: m.bundled,
init: m.resources.init.to_string_lossy().to_string(),
data: m
.resources
.data
.as_ref()
.map(|p| p.to_string_lossy().to_string()),
localization: m
.resources
.localization
.as_ref()
.map(|p| p.to_string_lossy().to_string()),
packages: m.packages.iter().map(|p| p.name.clone()).collect(),
})
})
.collect();
lua.push_str("\",\n })\n"); let lua = tmpl
} else { .render(minijinja::context!(mods => data))
lua.push_str(" return dofile(\""); .wrap_err("Failed to render template `mod_data.lua`")?;
lua.push_str(&resources.init.to_slash_lossy());
lua.push_str("\")\n");
}
lua.push_str(" end,\n packages = {\n"); tracing::debug!("mod_data.lua:\n{}", lua);
for pkg_info in &mod_info.packages { Ok(lua)
lua.push_str(" \"");
lua.push_str(&pkg_info.name);
lua.push_str("\",\n");
}
lua.push_str(" },\n },\n");
}
lua.push('}');
tracing::debug!("mod_data_lua:\n{}", lua);
lua
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -340,7 +333,7 @@ async fn build_bundles(state: Arc<ActionState>) -> Result<Vec<Bundle>> {
let span = tracing::debug_span!("Building mod data script"); let span = tracing::debug_span!("Building mod data script");
let _enter = span.enter(); let _enter = span.enter();
let lua = build_mod_data_lua(state.clone()); let lua = build_mod_data_lua(state.clone()).wrap_err("Failed to build Lua mod data")?;
tracing::trace!("Compiling mod data script"); tracing::trace!("Compiling mod data script");