Module:Entrypoint

From HorizonXI Wiki

Documentation for this module may be created at Module:Entrypoint/doc

--- Entrypoint templating wrapper for Scribunto packages.
--  The module generates an entrypoint function that can execute Scribunto
--  package calls in the template context. This allows a package to support
--  both direct and template invocations.
--  
--  @script             entrypoint
--  @release            stable
--  @author             [[User:8nml|8nml]]
--  @param              {table} package Scribunto package.
--  @error[85]          {string} 'you must specify a function to call'
--  @error[91]          {string} 'the function you specified did not exist'
--  @error[opt,95]      {string} '$2 is not a function'
--  @return             {function} Template entrypoint - @{main}.
--  @note               Parent frames are not available in Entrypoint's
--                      `frame`. This is because recursive (grandparent)
--                      frame access is impossible in legacy Scribunto
--                      due to [[mw:Manual:Parser#Empty-argument expansion
--                      cache|empty-argument expansion cache]] limitations.
--  @note               As Entrypoint enables template access rather than
--                      a new extension hook, it does not work with named
--                      numeric parameters such as `1=` or `2=`. This may
--                      result in unexpected behaviour such as Entrypoint
--                      and module errors.

--- Stateless, sequential Lua iterator.
--  @function           inext
--  @param              {table} t Invariant state to loop over.
--  @param              {number} i Control variable (current index).
--  @return[opt]        {number} Next index.
--  @return[opt]        {number|string|table|boolean} Next value.
--  @see                [[github:lua/lua/blob/v5.1.1/lbaselib.c#L247]]
local inext = select(1, ipairs{})

--- Check for MediaWiki version 1.25.
--  The concurrent Scribunto release adds a type check for package functions.
--  @variable           {boolean} func_check
--  @see                [[mw:MediaWiki 1.24/wmf7#Scribunto]]
local func_check = tonumber(mw.site.currentVersion:match('^%d+.%d+')) >= 1.25

--- MediaWiki error message getter.
--  Mimics Scribunto error formatting for script errors. 
--  @function           msg
--  @param              {string} key MediaWiki i18n message key.
--  @param[opt]         {string} fn_name Name of package function.
--  @return             {string} Formatted lowercase message.
--  @local
local function msg(key, fn_name)
    return select(1, mw.message.new(key)
        :plain()
        :match(':%s*(.-)[.۔。෴։።]?$')
        :gsub('^.', mw.ustring.lower)
        :gsub('$2', fn_name or '$2')
    )
end

--- Template entrypoint function generated by this module.
--  @function           main
--  @param              {Frame} frame Scribunto frame in module context.
--  @return             {string} Module output in template context.
return function(package) return function(f)
    local frame = f:getParent()

    local args_mt = {}
    local arg_cache = {}

    args_mt.__pairs = function()
        return next, arg_cache, nil
    end
    args_mt.__ipairs = function()
        return inext, arg_cache, 0
    end
    args_mt.__index = function(t, k)
        return arg_cache[k]
    end

    for key, val in pairs(frame.args) do
        arg_cache[key] = val
    end
    local fn_name = table.remove(arg_cache, 1)

    f.args = setmetatable({}, args_mt)
    frame.args = setmetatable({}, args_mt)

    if not fn_name then
        error(msg('scribunto-common-nofunction'))
    end

    fn_name = mw.text.trim(fn_name)

    if not package[fn_name] then
        error(msg('scribunto-common-nosuchfunction', fn_name))
    end

    if func_check and type(package[fn_name]) ~= 'function' then
        error(msg('scribunto-common-notafunction', fn_name))
    end

    return package[fn_name](frame)
end end