Module:FrameTools

From The Satanic Wiki
Revision as of 15:48, 24 April 2021 by Mediawiki>ExE Boss (Hoist type checks to function start)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

See Global Lua Modules/FrameTools

Subpages
{{#dpl: | namespace = Module | titlematch = FrameTools/% | skipthispage = false }}

-- <nowiki>
--------------------------------------------------------------------------------
-- This module contains helper functions for dealing with frame objects.
--
-- @script frameTools
-- @alias p
-- @author [[User:DarthKitty]]
-- @author [[User:Dessamator]]
--------------------------------------------------------------------------------
local p = {}
local util = require "libraryUtil"

--------------------------------------------------------------------------------
-- Frame methods are protected by a `checkSelf` function, which makes them more
-- difficult to copy.
--
-- @param {Frame|PseudoFrame} frame
--        The frame of pseudo-frame to copy.
-- @return {PseudoFrame}
--------------------------------------------------------------------------------
function p.copy(frame)
    util.checkType("frameTools.copy", 1, frame, "table")

    local copy = mw.clone(frame)

    -- Point methods on `copy` to their `frame` counterparts
    for methodName, method in pairs(frame) do
        if type(method) == "function" and methodName ~= "getParent" then
            copy[methodName] = function (copy, ...)
                return method(frame, ...)
            end
        end
    end

    -- This method needs special treatment
    function copy:getParent()
        local parent = frame:getParent()

        if parent then
            return p.copy(parent)
        end
    end

    return copy
end

--------------------------------------------------------------------------------
-- Creates a pseudo frame with some useful functions available in
-- [[mw:Extension:Scribunto]], e.g. `newChild`.
--
-- @param[opt] {Frame} frame
--             The frame that provides implementatinos for the `preprocess` method.
-- @param[opt] {table} parentArgs
--             The parameters available on `pseudoFrame:getParent().args`
-- @param[opt] {table} childArgs
--             The parameters available on `pseudoFrame.args`
-- @return {PseudoFrame}
--         The new pseudo-Frame
--------------------------------------------------------------------------------
function p.makePseudoFrame(frame, parentArgs, childArgs)
    util.checkType("frameTools.makePseudoFrame", 1, frame, "table", true)
    util.checkType("frameTools.makePseudoFrame", 2, parentArgs, "table", true)
    util.checkType("frameTools.makePseudoFrame", 3, childArgs, "table", true)

    local pseudoFrame = {parent = {}}
    local checkSelf = util.makeCheckSelfFunction("pseudoFrame", "frame", pseudoFrame, "PseudoFrame object")
    local parentFrame = pseudoFrame.parent
    local checkSelfParent = util.makeCheckSelfFunction("pseudoFrame", "frame", parentFrame, "parent PseudoFrame object")
    local pArgs = {}
    local pTitle
    local args = {}

    if frame then
        args = frame.args

        for name in pairs(frame) do
            pseudoFrame[name] = function (pseudoFrame, ...)
                return frame[name](frame, ...)
            end

            parentFrame[name] = function (parent, ...)
                return pseudoFrame[name](frame, ...)
            end
        end

        if type(frame.getParent) == "function" and frame:getParent() then
            pArgs = frame:getParent().args
            pTitle = frame:getParent():getTitle()
        end
    end

    parentFrame.args = mw.clone(parentArgs or pArgs)
    pseudoFrame.args = mw.clone(childArgs or args)
    pseudoFrame.title = mw.title.getCurrentTitle().text
    parentFrame.title = pTitle or ""

    function pseudoFrame:getTitle()
        checkSelf(self, "getTitle")

        return self.title
    end

    function parentFrame:getTitle()
        checkSelfParent(self, "getTitle")

        return self.title
    end

    function pseudoFrame:getParent()
        checkSelf(self, "getParent")

        return self.parent
    end

    function parentFrame:getParent()
        checkSelfParent(self, "getParent")

        return self.parent
    end

    -- Creates a new Frame object that is a child of the current frame, optional
    -- arguments and title.
    -- Syntax: frame:newChild{title = title, args = table}
    function pseudoFrame:newChild(attributes)
        checkSelf(self, "newChild")

        local tmpTable = p.makePseudoFrame(frame, self.args, attributes.args)

        tmpTable.parent.title = self.title
        tmpTable.title = attributes.title

        return tmpTable
    end

    function pseudoFrame:showargs()
        checkSelf(self, "showargs")

        local childParams = "Child parameters:\nKey \t Value"
        local parentParams = "Parent parameters:\nKey \t Value"

        if self.args then
            for k, v in pairs(self.args) do
                childParams = childParams .. "\n" .. k .. "\t" .. v
            end
        end

        if self.parent.args then
            for k, v in pairs(self.parent.args) do
                parentParams = parentParams .. "\n" .. k .. "\t" .. v
            end
        end

        return childParams .. "\n\n" .. parentParams
    end

    function pseudoFrame:setArgs(childArgs, parentArgs)
        checkSelf(self, "setArgs")

        if childArgs then
            self.args = childArgs or {}
        end

        if parentArgs then
           self.parent.args = parentArgs
        end
    end

    local function preprocessMock(parent) return function(self, opt)
        if parent then
            checkSelfParent(self, "expandTemplate")
        else
            checkSelf(self, "expandTemplate")
        end

        local text
        if type(opt) == "table" then
            text = opt.text
        else
            text = opt
        end
        text = tostring(text)

        return text
    end end
    if not pseudoFrame.preprocess then
        pseudoFrame.preprocess = preprocessMock()
    end
    if not pseudoFrame.parent.preprocess then
        pseudoFrame.parent.preprocess = preprocessMock("parent")
    end

    local function expandTemplateMock(parent) return function(self, opt)
        if parent then
            checkSelfParent(self, "expandTemplate")
        else
            checkSelf(self, "expandTemplate")
        end

        if type(opt) ~= "table" then
            error("frame:expandTemplate: the first parameter must be a table")
        end

        local title
        if opt.title == nil then
            error("frame:expandTemplate: a title is required")
        else
            title = tostring(opt.title)
        end

        local args
        if opt.args == nil then
            args = {}
        elseif type(opt.args) == "table" then
            args = opt.args
        else
            error("frame:expandTitle: args must be a table")
        end

        local keys = {}
        for k in pairs(args) do
            table.insert(keys, k)
        end
        table.sort(keys)

        local text = {}
        table.insert(text, "{{" .. title)
        local anonymous_index = 0
        for _, k in ipairs(keys) do
            table.insert(text, "|" .. (tonumber(k) == (anonymous_index + 1) and "" or (" " .. k .. " = ")) .. args[k])
            if tonumber(k) == (anonymous_index + 1) then anonymous_index = anonymous_index + 1 end
        end
        table.insert(text, "}}")
        text = table.concat(text, '\n')

        return text
    end end

    if not pseudoFrame.expandTemplate then
        pseudoFrame.expandTemplate = expandTemplateMock()
    end
    if not parentFrame.expandTemplate then
        parentFrame.expandTemplate = expandTemplateMock("parent")
    end

    if mw.getCurrentFrame() == nil then
        _G.mw.getCurrentFrame = function() return pseudoFrame end
    end

    return pseudoFrame
end

--------------------------------------------------------------------------------
-- Returns a frame-like object with the given arguments removed.
--
-- @param {Frame|PseudoFrame} frame
--        The Frame to copy
-- @param[opt] ...
--             The arguments to omit from the new `Frame`'s `args` property
-- @return {PseudoFrame}
--------------------------------------------------------------------------------
function p.removeArgs(frame, ...)
    util.checkType("frameTools.removeArgs", 1, frame, "table")

    local pseudoFrame = p.copy(frame)
    local args = pseudoFrame.args
    local metatable = getmetatable(args)

    -- disable arg caching
    metatable.__index = nil
    metatable.__pairs = nil

    -- remove args
    for _, arg in ipairs{...} do
        args[arg] = nil
    end

    return pseudoFrame
end

--------------------------------------------------------------------------------
-- A frame-like object that provides mock implementations for the most common
-- @{Frame|mw.frame} instance methods and properties
--
-- @type {Frame} PseudoFrame
--------------------------------------------------------------------------------

return p

-- </nowiki>
-- (Add categories here.)