diff --git a/.commitlintrc.yml b/.commitlintrc.yml index bcedfff22..4eae08e9c 100644 --- a/.commitlintrc.yml +++ b/.commitlintrc.yml @@ -51,6 +51,7 @@ rules: - pdf - readme - release + - rusile - settings - shapers - tooling diff --git a/Cargo.lock b/Cargo.lock index 670d1c9b5..974f707ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1046,9 +1046,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -1143,15 +1143,17 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.9" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +checksum = "0f6ddbd668297c46be4bdea6c599dcc1f001a129586272d53170b7ac0a62961e" dependencies = [ + "anyhow", "bstr", + "either", "mlua-sys", "mlua_derive", "num-traits", - "once_cell", + "parking_lot", "rustc-hash", ] @@ -1170,9 +1172,9 @@ dependencies = [ [[package]] name = "mlua_derive" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09697a6cec88e7f58a02c7ab5c18c611c6907c8654613df9cc0192658a4fb859" +checksum = "2cfc5faa2e0d044b3f5f0879be2920e0a711c97744c42cf1c295cb183668933e" dependencies = [ "itertools", "once_cell", @@ -1346,6 +1348,14 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" +[[package]] +name = "rusile" +version = "0.15.5" +dependencies = [ + "mlua", + "sile", +] + [[package]] name = "rust-embed" version = "8.5.0" @@ -1521,6 +1531,7 @@ dependencies = [ "harfbuzz-sys", "mlua", "rust-embed", + "semver", "vergen", ] diff --git a/Cargo.toml b/Cargo.toml index c26c94cf0..77c55511f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,39 @@ -[package] -name = "sile" +[workspace] +resolver = "2" +members = [ + ".", + "rusile", +] + +[workspace.package] version = "0.15.5" edition = "2021" rust-version = "1.71.0" -description = "Simon’s Improved Layout Engine" authors = [ "Simon Cozens", "Caleb Maclennan ", "Olivier Nicole", "Didier Willis" ] -readme = "README.md" homepage = "https://sile-typesetter.org" repository = "https://github.com/sile-typesetter/sile" license = "MIT" + +[package] +name = "sile" +description = "Simon’s Improved Layout Engine" +readme = "README.md" build = "build-aux/build.rs" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[package.metadata.bacon.jobs] +cmd = [ "cargo", "build", "--color", "always" ] [package.metadata.typos.default] locale = "en-us" @@ -91,6 +110,10 @@ zsh = [ "completions" ] [profile.release] lto = true +[workspace.dependencies.mlua] +version = "0.10.0" +features = [ "anyhow" ] + [dependencies.anyhow] version = "1.0" @@ -100,7 +123,7 @@ optional = true features = [ "derive", "string", "wrap_help" ] [dependencies.mlua] -version = "0.9" +workspace = true features = [ "macros" ] [dependencies.rust-embed] @@ -112,6 +135,9 @@ features = [ "include-exclude" ] version = "0.5" optional = true +[dependencies.semver] +version = "1.0" + [build-dependencies.clap_complete] version = "4.4" optional = true diff --git a/Makefile.am b/Makefile.am index ea1d6367f..4bf08a194 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,18 +55,21 @@ endif $(MANUAL): $(FIGURES) -BUILT_LUA_SOURCES = core/features.lua core/pathsetup.lua core/version.lua +BUILT_SOURCES_LUA = core/features.lua core/pathsetup.lua core/version.lua +RUSILE_SOURCES = rusile/Cargo.toml rusile/src/lib.rs bin_PROGRAMS = sile bin_SCRIPTS = sile-lua nodist_man_MANS = dist_man_MANS = sile-lua.1 -sile_SOURCES = src/bin/sile.rs src/lib.rs src/cli.rs +sile_SOURCES = src/bin/sile.rs src/lib.rs src/cli.rs src/types.rs src/types/semver.rs EXTRA_sile_SOURCES = if !EMBEDDED_RESOURCES nobase_dist_pkgdata_DATA = $(SILEDATA) $(LUALIBRARIES) -nobase_nodist_pkgdata_DATA = $(BUILT_LUA_SOURCES) $(LUAMODULES) -endif +nobase_nodist_pkgdata_DATA = $(BUILT_SOURCES_LUA) $(LUAMODULES) +pkglib_LIBRARIES = rusile.so +rusile_so_SOURCES = $(RUSILE_SOURCES) +endif !EMBEDDED_RESOURCES dist_doc_DATA = README.md CHANGELOG.md dist_pdf_DATA = $(_MANUAL) dist_license_DATA = LICENSE.md @@ -83,7 +86,13 @@ if EMBEDDED_RESOURCES EXTRA_DIST += $(SILEDATA) $(LUALIBRARIES) endif -BUILT_SOURCES = $(BUILT_LUA_SOURCES) Makefile-distfiles +BUILT_SOURCES = $(BUILT_SOURCES_LUA) Makefile-distfiles + +if SHARED +EXTRA_RUNTIME_DEPS = $(pkglib_LIBRARIES) +else !SHARED +EXTRA_RUNTIME_DEPS = +endif CLEANFILES = $(MANUAL) @@ -122,11 +131,15 @@ $(CARGO_BIN): justenough/.libs/justenoughicu.a $(CARGO_BIN): justenough/.libs/justenoughlibtexpdf.a $(CARGO_BIN): justenough/.libs/svg.a $(CARGO_BIN): libtexpdf/.libs/libtexpdf.a +if !EMBEDDED_RESOURCES +$(CARGO_BIN): rusile.so +endif !EMBEDDED_RESOURCES src/embed-includes.rs: Makefile-distfiles { - echo $(BUILT_LUA_SOURCES) - $(GREP) -E '^(SILEDATA|LUALIBRARIES|LUAMODULES) = ' $< + echo $(BUILT_SOURCES_LUA) + $(SED) -ze 's/\\\n\t//g' $< | + $(GREP) -E '^(SILEDATA|LUALIBRARIES|LUAMODULES) = ' } | $(SED) -E -e 's/^.* = //;s/ /\n/g' | while read file; do @@ -146,6 +159,7 @@ else MLUAVER = lua$(LUA_SHORT_VERSION) endif CARGO_FEATURE_ARGS = --features $(MLUAVER) +RUSILE_FEATURE_ARG = --features $(MLUAVER) if !SYSTEM_LUA_SOURCES CARGO_FEATURE_ARGS += --features vendored @@ -159,6 +173,10 @@ if FONT_VARIATIONS CARGO_FEATURE_ARGS += --features variations endif +rusile.so: $(rusile_so_SOURCES) $(bin_PROGRAMS) + $(CARGO_ENV) $(CARGO) build $(CARGO_VERBOSE) $(RUSILE_FEATURE_ARG) $(CARGO_RELEASE_ARGS) -p rusile + $(INSTALL) @builddir@/target/@RUST_TARGET_SUBDIR@/lib$@ $@ + DEPDIR := .deps LOCALFONTS := FONTCONFIG_FILE=$(PWD)/fontconfig.conf LOCALPATHS := SILE_PATH="$(PWD);libtexpdf/.libs;justenough/.libs" @@ -198,7 +216,7 @@ sile-%.md: CHANGELOG.md check: selfcheck .PHONY: selfcheck -selfcheck: | $(bin_PROGRAMS) $(_BUILT_SUBDIRS) +selfcheck: | $(bin_PROGRAMS) $(_BUILT_SUBDIRS) $(EXTRA_RUNTIME_DEPS) output=$$(mktemp -t selfcheck-XXXXXX.pdf) trap '$(RM) $$output' EXIT HUP TERM echo "foo" | $(LOCALPATHS) ./$(bin_PROGRAMS) -o $$output - @@ -249,9 +267,6 @@ _FORCED = $(and $(SILE_COVERAGE)$(CLEAN),force) _TEST_DEPS = $(and $$(filter tests/%,$@),$(addprefix .fonts/,$(TESTFONTFILES))) _DOCS_DEPS = $(and $$(filter documentation/%,$@),$(addprefix .fonts/,$(DOCSFONTFILES))) -# TODO: remove _BUILT_SUBDIRS hack and replace it with something sensible when -# these subdirs don't do crazy things like copying files outside of their own trees! -_BUILT_SUBDIRS = .built-subdirs _SUBDIR_TELLS = if SHARED @@ -282,7 +297,7 @@ CLEANFILES += $(_BUILT_SUBDIRS) $(_SUBDIR_TELLS): $(MAKE) $(AM_MAKEFLAGS) all-recursive -patterndeps = $(_FORCED) $(_TEST_DEPS) $(_DOCS_DEPS) | $(bin_PROGRAMS) $(DEPDIRS) $(LUAMODLOCK) $(_BUILT_SUBDIRS) +patterndeps = $(_FORCED) $(_TEST_DEPS) $(_DOCS_DEPS) | $(bin_PROGRAMS) $(EXTRA_RUNTIME_DEPS) $(DEPDIRS) $(LUAMODLOCK) $(_BUILT_SUBDIRS) %.pdf: %.sil $$(patterndeps) $(runsile) @@ -365,8 +380,15 @@ if !SYSTEM_LUAROCKS packagepath+=(./lua_modules/share/lua/$(LUA_VERSION)/?{,/init}.lua) packagecpath+=(./lua_modules/lib/lua/$(LUA_VERSION)/?.$(SHARED_LIB_EXT)) endif -# Note: use of --lua causes this to be passed back through a shell loosing one layer of quoting. Drop single quotes if removing. +# Note: Busted tests can't run with out static build since the Rusile module is +# only available embedded into it. We can test on the dynamic builds where it +# is available as a use space module. This could change if we enable a lua vm +# pass through mode and use that as busted's interpreter. +if SHARED +# Note: use of --lua causes this to be passed back through a shell loosing one +# layer of quoting. Drop single quotes if removing. $(LOCALFONTS) $(BUSTED) --lua=$(LUA) --lpath="'$${packagepath[*]};;'" --cpath="'$${packagecpath[*]};;'" $(BUSTEDFLAGS) . +endif coverage: export SILE_COVERAGE=1 coverage: BUSTEDFLAGS += -c diff --git a/build-aux/que_rust_boilerplate.am b/build-aux/que_rust_boilerplate.am index 93514b945..a1ba65af4 100644 --- a/build-aux/que_rust_boilerplate.am +++ b/build-aux/que_rust_boilerplate.am @@ -54,7 +54,7 @@ $(COMPLETIONS_OUT_DIR)/_$(TRANSFORMED_PACKAGE_NAME).ps1: $(CARGO_BIN) | $(COMPLE $(COMPLETIONS_OUT_DIR)/_$(TRANSFORMED_PACKAGE_NAME): $(CARGO_BIN) | $(COMPLETIONS_OUT_DIR) $(INSTALL) -m755 $$(cat $(_RUST_OUT))/$(COMPLETIONS_OUT_DIR)/_$(PACKAGE_NAME) $@ -$(_RUST_OUT) $(CARGO_BIN): $(@PACKAGE_VAR@_SOURCES) $(EXTRA_@PACKAGE_VAR@_SOURCES) +$(_RUST_OUT) $(CARGO_BIN): $(@PACKAGE_VAR@_SOURCES) $(nodist_@PACKAGE_VAR@_SOURCES) $(EXTRA_@PACKAGE_VAR@_SOURCES) set -e export AUTOTOOLS_DEPENDENCIES="$^" $(CARGO_ENV) $(CARGO) build $(CARGO_VERBOSE) $(CARGO_FEATURE_ARGS) $(CARGO_RELEASE_ARGS) diff --git a/configure.ac b/configure.ac index 74e42b2da..34aac5680 100644 --- a/configure.ac +++ b/configure.ac @@ -27,6 +27,10 @@ QUE_PROGVAR([pdfinfo]) QUE_PROGVAR([sort]) QUE_PROGVAR([xargs]) +# Disable ranlib to avoid it being run on our rusile.so module, already LTO +# optimized but libtool wants to relink it... +RANLIB=: + LT_PREREQ([2.2]) LT_INIT([dlopen]) diff --git a/core/utilities/init.lua b/core/utilities/init.lua index 7f2026417..e56b26265 100644 --- a/core/utilities/init.lua +++ b/core/utilities/init.lua @@ -4,7 +4,7 @@ local bitshim = require("bitshim") local luautf8 = require("lua-utf8") -local semver = require("semver") +local semver = require("rusile").semver local utilities = {} diff --git a/lua-libraries/semver.lua b/lua-libraries/semver.lua deleted file mode 100644 index 867eec95f..000000000 --- a/lua-libraries/semver.lua +++ /dev/null @@ -1,46 +0,0 @@ --- Loosely inspired from https://github.com/kikito/semver.lua --- (MIT License (c) 2011 Enrique García Cota) --- but simplified to our bare needs. - -local semver = {} -local mt = {} - -function mt:__eq(other) - return self.major == other.major and - self.minor == other.minor and - self.patch == other.patch -end - -function mt:__lt(other) - if self.major ~= other.major then return self.major < other.major end - if self.minor ~= other.minor then return self.minor < other.minor end - if self.patch ~= other.patch then return self.patch < other.patch end - return false -end - -function mt:__le(other) - if self.major ~= other.major then return self.major <= other.major end - if self.minor ~= other.minor then return self.minor <= other.minor end - if self.patch ~= other.patch then return self.patch <= other.patch end - return true -end - -function mt:__tostring() - return ("%d.%d.%d"):format(self.major, self.minor, self.patch) -end - -local function new (vstr) - local major, minor, patch = vstr:match("^v?(%d+)%.(%d+)%.(%d+)") - local result = { major = tonumber(major), minor = tonumber(minor), patch = tonumber(patch) } - if not result.major and not result.minor and not result.patch then - error("Invalid version string: "..vstr) - end - local o = setmetatable(result, mt) - return o -end - -setmetatable(semver, { - __call = function(_, ...) return new(...) end -}) - -return semver diff --git a/packages/retrograde/init.lua b/packages/retrograde/init.lua index be7658f27..07bd52215 100644 --- a/packages/retrograde/init.lua +++ b/packages/retrograde/init.lua @@ -3,7 +3,7 @@ local base = require("packages.base") local package = pl.class(base) package._name = "retrograde" -local semver = require("semver") +local semver = require("rusile").semver local semver_descending = function (a, b) a, b = semver(a), semver(b) diff --git a/rusile/Cargo.toml b/rusile/Cargo.toml new file mode 100644 index 000000000..8606b30ee --- /dev/null +++ b/rusile/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "rusile" +description = "Rusty components for the SILE typesetter" +readme = "README.md" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +crate-type = ["rlib", "cdylib", "staticlib"] + +[dependencies.sile] +path = ".." + +[features] +default = [] +lua54 = [ "mlua/lua54" ] +lua53 = [ "mlua/lua53" ] +lua52 = [ "mlua/lua52" ] +lua51 = [ "mlua/lua51" ] +luajit = [ "mlua/luajit" ] +vendored = [ "mlua/vendored" ] + +[dependencies.mlua] +workspace = true +features = [ "macros", "module" ] diff --git a/rusile/src/lib.rs b/rusile/src/lib.rs new file mode 100644 index 000000000..e685f3d3d --- /dev/null +++ b/rusile/src/lib.rs @@ -0,0 +1,13 @@ +// Thin wrapper around user facing functions implemented in Rust to expose them in a loadable Lua +// module separate from the Rust CLI. + +#![crate_type = "cdylib"] +#![crate_type = "rlib"] +#![crate_type = "staticlib"] + +use mlua::prelude::*; + +#[mlua::lua_module] +fn rusile(lua: &Lua) -> LuaResult { + sile::get_rusile_exports(lua) +} diff --git a/spec/rusile_spec.lua b/spec/rusile_spec.lua new file mode 100644 index 000000000..114c90ff0 --- /dev/null +++ b/spec/rusile_spec.lua @@ -0,0 +1,36 @@ +SILE = require("core.sile") + +local rusile = require("rusile") + +local callable = require("luassert.util").callable + +describe("rusile", function () + it("should exist", function () + assert.is.truthy(rusile) + end) + + describe("semver", function () + local semver = rusile.semver + + it("constructor should exist", function () + assert.is.truthy(callable(semver)) + end) + + describe("instance", function () + local a = semver("1.3.5") + local b = semver("1.3.5") + local c = semver("2.4.6") + + it("should evaluate comparisons", function () + assert.is.equal(a, b) + assert.is.truthy(a == b) + assert.is_not.equal(a, c) + assert.is_not.truthy(a == c) + assert.is.truthy(a < c) + assert.is.truthy(c > b) + assert.is.truthy(a <= b) + assert.is.truthy(c >= b) + end) + end) + end) +end) diff --git a/src/embed.rs.in b/src/embed.rs.in index e40c425b0..2b7e7e6c4 100644 --- a/src/embed.rs.in +++ b/src/embed.rs.in @@ -7,9 +7,24 @@ use std::str; #[derive(RustEmbed)] #[folder = "."] #[exclude = ".*"] -#[exclude = "*~"] +#[exclude = "*.a"] +#[exclude = "*.abnf"] +#[exclude = "*.ac"] +#[exclude = "*.am"] #[exclude = "*.in"] -#[exclude = "Make*"] +#[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/*"] @@ -17,11 +32,10 @@ use std::str; #[exclude = "documentation/*"] #[exclude = "justenough/*"] #[exclude = "libtexpdf/*"] -#[exclude = "libtexpdf/*"] -#[exclude = "libtool"] #[exclude = "node_modules/*"] -#[exclude = "rust-toolchain"] -#[exclude = "sile*"] +#[exclude = "rusile/*"] +#[exclude = "rust-api-docs/*"] +#[exclude = "spec/*"] #[exclude = "src/*"] #[exclude = "target/*"] #[exclude = "tests/*"] @@ -42,15 +56,26 @@ extern "C-unwind" { /// 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_loader(lua: &Lua) { +pub fn inject_embedded_loaders(lua: &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"), }; - loaders - .push(LuaFunction::wrap(|lua, module: String| unsafe { + + 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 embeded 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) @@ -70,10 +95,12 @@ pub fn inject_embedded_loader(lua: &Lua) { "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(LuaFunction::wrap(|lua, module: String| { + 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! { @@ -81,7 +108,7 @@ pub fn inject_embedded_loader(lua: &Lua) { }) .eval() .unwrap(); - let luaversion: &str = luaversion.to_str().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); @@ -101,36 +128,40 @@ pub fn inject_embedded_loader(lua: &Lua) { } } match resource_option { - Some(module) => { - return LuaFunction::wrap(move |lua, ()| { + 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>(()) + lua.load(data).call::(()) }) - .into_lua(lua) - } + .map(LuaValue::Function), + None => format!("Module '{module}' is not embedded in Rust binary").into_lua(lua), } - })) + }) .unwrap(); - loaders - .push(LuaFunction::wrap(|lua, module: String| { + 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) => LuaFunction::wrap(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)) + 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::(()) }) - .call::<_, LuaValue>(()) - }) - .into_lua(lua), - None => format!("FTL resource '{module_path}' is not embedded in Rust binary") + .map(LuaValue::Function), + _ => format!("FTL resource '{module_path}' is not embedded in Rust binary") .into_lua(lua), } - })) + }) .unwrap(); + loaders.push(embedded_ftl_loader).unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index 77af6a24e..689a0ef2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ // rust-embed include attributes have issues with lots of matches... #![recursion_limit = "2048"] +#[cfg(not(feature = "static"))] use mlua::chunk; use mlua::prelude::*; #[cfg(not(feature = "static"))] @@ -12,12 +13,14 @@ pub mod cli; #[cfg(feature = "static")] pub mod embed; +pub mod types; + pub type Result = anyhow::Result; pub fn start_luavm() -> crate::Result { let lua = unsafe { Lua::unsafe_new() }; #[cfg(feature = "static")] - crate::embed::inject_embedded_loader(&lua); + crate::embed::inject_embedded_loaders(&lua); inject_paths(&lua); load_sile(&lua); inject_version(&lua); @@ -26,7 +29,10 @@ pub fn start_luavm() -> crate::Result { pub fn inject_paths(lua: &Lua) { #[cfg(feature = "static")] - lua.load(r#"require("core.pathsetup")"#).exec().unwrap(); + lua.load(r#"require("core.pathsetup")"#) + .set_name("relative pathsetup loader") + .exec() + .unwrap(); #[cfg(not(feature = "static"))] { let datadir = env!("CONFIGURE_DATADIR").to_string(); @@ -45,11 +51,18 @@ pub fn inject_paths(lua: &Lua) { dofile("./core/pathsetup.lua") end }) + .set_name("hard coded pathsetup loader") .exec() .unwrap(); } } +pub fn get_rusile_exports(lua: &Lua) -> LuaResult { + let exports = lua.create_table()?; + exports.set("semver", LuaFunction::wrap_raw(types::semver::semver))?; + Ok(exports) +} + pub fn inject_version(lua: &Lua) { let sile: LuaTable = lua.globals().get("SILE").unwrap(); let mut full_version: String = sile.get("full_version").unwrap(); @@ -60,7 +73,7 @@ pub fn inject_version(lua: &Lua) { pub fn load_sile(lua: &Lua) { let entry: LuaString = lua.create_string("core.sile").unwrap(); let require: LuaFunction = lua.globals().get("require").unwrap(); - require.call::(entry).unwrap(); + require.call::(entry).unwrap(); } pub fn version() -> crate::Result { @@ -133,35 +146,34 @@ pub fn run( has_input_filename = true; } if let Some(options) = options { - // TODO: when mlua v0.10 merges, adapt this like the uses parsing to avoid chunking + let parameters: LuaAnyUserData = sile.get::("parserBits")?.get("parameters")?; + let input_options: LuaTable = sile_input.get("options")?; for option in options.iter() { - let option = lua.create_string(option)?; - lua.load(chunk! { - local parameter = SILE.parserBits.parameters:match($option); - SILE.input.options = pl.tablex.merge(SILE.input.options, parameter, true) - }) - .eval::<()>()?; + let parameters: LuaTable = parameters + .call_method("match", lua.create_string(option)?) + .context("failed to call `parameters:match()`")?; + for parameter in parameters.pairs::() { + let (key, value) = parameter?; + let _ = input_options.set(key, value); + } } } if let Some(modules) = uses { - // let parser_bits: LuaTable = sile.get("parserBits")?; - // let cliuse: LuaAnyUserData = parser_bits.get("cliuse")?; - // sile_input.get("uses")?; + let cliuse: LuaAnyUserData = sile.get::("parserBits")?.get("cliuse")?; + let input_uses: LuaTable = sile_input.get("uses")?; for module in modules.iter() { let module = lua.create_string(module)?; - lua.load(chunk! { - local spec = SILE.parserBits.cliuse:match($module); - table.insert(SILE.input.uses, spec) - }) - .eval::<()>()?; - // let spec = cliuse.call_function::<_, _, _>("match", module); + let spec: LuaTable = cliuse + .call_method::<_>("match", module) + .context("failed to call `cliuse:match()`")?; + let _ = input_uses.push(spec); } } if !quiet { eprintln!("{full_version}"); } let init: LuaFunction = sile.get("init")?; - init.call::<_, ()>(())?; + init.call::(())?; if let Some(inputs) = inputs { let input_filenames: LuaTable = lua.create_table()?; for input in inputs.iter() { @@ -183,20 +195,20 @@ pub fn run( let spec = spec?; let module: LuaString = spec.get("module")?; let options: LuaTable = spec.get("options")?; - r#use.call::<(LuaString, LuaTable), ()>((module, options))?; + r#use.call::<(LuaString, LuaTable)>((module, options))?; } let input_filenames: LuaTable = sile_input.get("filenames")?; let process_file: LuaFunction = sile.get("processFile")?; for file in input_filenames.sequence_values::() { - process_file.call::(file?)?; + process_file.call::(file?)?; } let finish: LuaFunction = sile.get("finish")?; - finish.call::<_, ()>(())?; + finish.call::(())?; } else { let repl_module: LuaString = lua.create_string("core.repl")?; let require: LuaFunction = lua.globals().get("require")?; - let repl: LuaTable = require.call::(repl_module)?; - repl.call_method::<_, ()>("enter", ())?; + let repl: LuaTable = require.call::(repl_module)?; + repl.call_method::("enter", ())?; } Ok(()) } diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 000000000..b658cff6c --- /dev/null +++ b/src/types.rs @@ -0,0 +1 @@ +pub mod semver; diff --git a/src/types/semver.rs b/src/types/semver.rs new file mode 100644 index 000000000..5835e146d --- /dev/null +++ b/src/types/semver.rs @@ -0,0 +1,66 @@ +use mlua::prelude::*; +use semver::Version; +use std::ops::Deref; + +#[derive(Clone, Debug)] +pub struct Semver { + version: Version, +} + +impl Semver { + pub fn new(version: &str) -> crate::Result { + let version = version.strip_prefix("v").unwrap_or(version); + Ok(Self { + version: Version::parse(version)?, + }) + } +} + +// TODO: cfg gate this so it only ends up in Lua module? +pub fn semver(version: String) -> crate::Result { + Semver::new(&version) +} + +impl Deref for Semver { + type Target = Version; + fn deref(&self) -> &Version { + &self.version + } +} + +impl LuaUserData for Semver { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("major", |_, this| Ok(this.version.major)); + fields.add_field_method_get("minor", |_, this| Ok(this.version.minor)); + fields.add_field_method_get("patch", |_, this| Ok(this.version.patch)); + } + + fn add_methods>(methods: &mut M) { + methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| { + Ok(this.version.to_string()) + }); + + methods.add_meta_method(LuaMetaMethod::Eq, |_, this, other: Self| { + Ok(this.version == other.version) + }); + + methods.add_meta_method(LuaMetaMethod::Le, |_, this, other: Self| { + Ok(this.version <= other.version) + }); + + methods.add_meta_method(LuaMetaMethod::Lt, |_, this, other: Self| { + Ok(this.version < other.version) + }); + } +} + +impl FromLua for Semver { + fn from_lua(value: LuaValue, _: &Lua) -> LuaResult { + match value { + LuaValue::UserData(ud) => Ok(ud.borrow::()?.clone()), + LuaValue::Table(_t) => todo!("implement for legacy Lua table based implementation"), + LuaValue::Nil => Ok(Semver::new("0.0.0")?), + _ => unreachable!(), + } + } +}