Module:User:Surjection/luasubst
- This module sandbox lacks a documentation subpage. You may create it.
- Useful links: root page • root page’s subpages • links • transclusions • testcases • user page • user talk page • userspace
This is a private module sandbox of Surjection, for their own experimentation. Items in this module may be added and removed at Surjection's discretion; do not rely on this module's stability.
local export = {}
local function isPositiveInteger(v)
return type(v) == 'number' and v >= 1 and math.floor(v) == v and v < math.huge
end
local function shift_args(args, n)
local newargs = {}
for k, v in pairs(args) do
if isPositiveInteger(k) then
if k > n then
newargs[k - n] = v
end
else
newargs[k] = v
end
end
return newargs
end
local PseudoframeArgument = {}
function PseudoframeArgument.expand(obj)
return obj["_expand"](obj)
end
function PseudoframeArgument.new(frame, obj)
return setmetatable({ ["_value"] = obj, ["_expand"] = function(obj) return frame:preprocess(obj) end }, PseudoframeArgument)
end
PseudoframeArgument.__index = PseudoframeArgument
local Pseudoframe = {}
local forwarded_methods = {
"callParserFunction",
"expandTemplate",
"extensionTag",
"getTitle",
"newChild",
"preprocess",
"newParserValue",
"newTemplateParserValue",
}
for _, name in ipairs(forwarded_methods) do
Pseudoframe[name] = function (pf, ...)
local f = pf["_frame"]
return f[name](f, ...)
end
end
function Pseudoframe.getArgument(pf, arg)
if type(arg) == "table" then
return pf:getArgument(arg.name)
end
local arg = pf.args[arg]
if arg == nil then return nil end
return PseudoframeArgument.new(f, arg)
end
function Pseudoframe.argumentPairs(pf)
return pairs(pf.args)
end
function Pseudoframe.getParent(pf)
if pf._parent then
return pf._parent
else
return pf._frame:getParent()
end
end
function Pseudoframe.new(frame, args, parent)
return setmetatable({ ["_frame"] = frame, ["args"] = args, ["_parent"] = parent }, Pseudoframe)
end
Pseudoframe.__index = Pseudoframe
local function trampoline(module_name, entrypoint, pframe)
return mw.text.nowiki(require(module_name)[entrypoint](pframe))
end
local function clean_traceback(msg)
local tb = debug.traceback(msg, 3)
tb = mw.ustring.gsub(tb, "Module:User:Surjection/luasubst:.+", "")
tb = mw.ustring.gsub(tb, "package.lua:.+", "")
tb = mw.ustring.gsub(tb, "mw.lua:.+", "")
return mw.text.trim(tb)
end
local function require_tracked(trackstring, old_require)
return function (name)
trackstring[1] = trackstring[1] .. clean_traceback("Module imported " .. name) .. "\n\n"
return old_require(name)
end
end
local function trampoline_track_requires(module_name, entrypoint, pframe)
local old_require = require
local slot = {""}
require = require_tracked(slot, old_require)
local result = old_require(module_name)[entrypoint](pframe)
require = old_require
return mw.text.tag("pre", {}, slot[1])
end
local function do_subst_explicit(frame, trampoline)
local module_name = "Module:" .. frame.args[1]
local entrypoint = frame.args[2]
local pframe = Pseudoframe.new(frame, shift_args(frame.args, 2), nil)
local result = trampoline(module_name, entrypoint, pframe)
return result
end
local function do_subst_implicit(frame, trampoline)
local parent = frame:getParent()
local template_name = parent.args[1]
local template_args = shift_args(parent.args, 1)
local pseudoparent = Pseudoframe.new(parent, template_args, nil)
local template_title = mw.title.new(template_name, 10)
if template_title.isRedirect then
template_title = template_title.redirectTarget
end
local template_source = template_title:getContent()
template_source = mw.ustring.gsub(template_source, "<noinclude>.-</noinclude>", "")
template_source = mw.ustring.gsub(template_source, "<includeonly>(.-)</includeonly>", "%1")
template_source = mw.ustring.gsub(template_source, ".-<onlyinclude>(.-)</onlyinclude>.*", "%1")
local ok, _, invoke_text = mw.ustring.find(template_source, "^(%b{})")
if not ok then error("Template does not begin with an invoke or a transclusion!") end
local ok, _, invoke_params = mw.ustring.find(invoke_text, "^{{#invoke:(.+)}}")
if not ok then error("Template does not begin with an invoke!") end
local ok, _, module_name, entrypoint, module_params = mw.ustring.find(invoke_params, "^([^|]+)|([^|]+)|(.+)")
if not ok then
ok, _, module_name, entrypoint = mw.ustring.find(invoke_params, "^([^|]+)|([^|]+)$")
if ok then
module_params = nil
else
error("Could not parse invoke!")
end
end
local parsed_args = {}
if module_params then
local parseTemplate = require("Module:User:Surjection/templateparser").parseTemplate
local parsed_ok
parsed_ok, parsed_args = parseTemplate("{{args|" .. module_params .. "}}")
if not parsed_ok then error("Could not parse module invoke parameters!") end
for k, v in pairs(parsed_args) do
if v:find("%[") or v:find("{") then
parsed_args[k] = frame:preprocess(v)
end
end
end
module_name = "Module:" .. module_name
local pframe = Pseudoframe.new(frame, parsed_args, pseudoparent)
local result = trampoline(module_name, entrypoint, pframe)
return result
end
function export.subst(frame)
return do_subst_explicit(frame, trampoline)
end
function export.subst_universal(frame)
return do_subst_implicit(frame, trampoline)
end
function export.subst_universal_track_requires(frame)
return do_subst_implicit(frame, trampoline_track_requires)
end
return export