Module:Utility functions

From Feed The Beast Wiki
Jump to: navigation, search

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

local util = {}

local type, pairs, ipairs = type, pairs, ipairs

function util.table_print(tt, indent, done)
    done = done or {}
    indent = indent or 0
    if type(tt) == "table" then
        local sb = {}
        for key, value in pairs(tt) do
            table.insert(sb, string.rep(" ", indent)) -- indent it
            if type(value) == "table" and not done[value] then
                done[value] = true
                if type(key) ~= "number" then
                    table.insert(sb, string.format("%s = ", util.to_string(key)))
                end
                table.insert(sb, "{\n")
                table.insert(sb, util.table_print(value, indent + 2, done))
                table.insert(sb, string.rep(" ", indent)) -- indent it
                table.insert(sb, "}\n")
            elseif "number" == type(key) then
                table.insert(sb, string.format("%s\n", util.to_string(value)))
            else
                table.insert(sb, string.format('%s = "%s"\n', util.to_string(key), util.to_string(value)))
            end
        end
        return table.concat(sb)
    else
        return tt .. "\n"
    end
end

function util.to_string(tbl)
    if "nil" == type(tbl) then
        return tostring("nil")
    elseif "string" == type(tbl) then
        return '"' .. tostring(tbl) .. '"'
    elseif "table" == type(tbl) then
        return util.table_print(tbl)
    else
        return tostring(tbl)
    end
end

function util.trim(s)
    -- from PiL2 20.4
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

local function __genOrderedIndex(t)
    local orderedIndex = {}
    for key in pairs(t) do
        table.insert(orderedIndex, key)
    end
    table.sort(
        orderedIndex,
        function(left, right)
            if type(left) == "table" then
                left = left[1]
            end
            if type(right) == "table" then
                right = right[1]
            end
            return left < right
        end
    )
    return orderedIndex
end

local function orderedNext(t, state)
    -- Equivalent of the next function, but returns the keys in the alphabetic
    -- order. We use a temporary ordered key table that is stored in the
    -- table being iterated.

    --print("orderedNext: state = "..tostring(state) )
    if state == nil then
        -- the first time, generate the index
        t.__orderedIndex = __genOrderedIndex(t)
        key = t.__orderedIndex[1]
        return key, t[key]
    end
    -- fetch the next value
    key = nil
    for i = 1, #t.__orderedIndex do
        if t.__orderedIndex[i] == state then
            key = t.__orderedIndex[i + 1]
        end
    end

    if key then
        return key, t[key]
    end

    -- no more value to return, cleanup
    t.__orderedIndex = nil
    return
end

function util.orderedPairs(t)
    -- Equivalent of the pairs() function on tables. Allows to iterate
    -- in order
    return orderedNext, t, nil
end

local langnames = nil
local code = function(title)
    local subPage = (title or mw.title.getCurrentTitle()).subpageText:lower()
    if not langNames then
        langNames = require([[Module:Language/Names]])
    end
    if langNames[subPage] then
        return subPage
    end
end
function util.pageSuffix()
    local langCode = code()
    if langCode then
        return "/" .. langCode
    end
    return "/en"
end

function util.compact(tab)
    local keys = {}
    local n = 0
    for k, v in pairs(tab) do
        n = n + 1
        keys[n] = k
    end
    local out = {}
    n = 0
    for k, v in ipairs(keys) do
        n = n + 1
        out[n] = tab[v]
    end
    return out
end

-- this is exactly as lua-users wiki defined it. Never mind the odd gsub argument.
function util.interp(s, tab)
    return (s:gsub(
        "($%b{})",
        function(w)
            return tab[w:sub(3, -2)] or w
        end
    ))
end

local function argOr(name, default)
    local frame = mw.getCurrentFrame()
    if frame == nil then
        return default
    end
    return frame.args[name]
end

-- returns (true, module) or (false, message)
function util.requireDataLocalized(name)
    -- So many ors because tilesheets somehow calls functions that call this *without a frame*
    local forceUntranslated = argOr("forceUntranslated", false)
    local moduleName = name
    if not forceUntranslated then
        moduleName = moduleName .. util.pageSuffix()
    end

    local success, data =
        pcall(
        function()
            return mw.loadData(moduleName)
        end
    )

    if not success then
        success, data =
            pcall(
            function()
                return mw.loadData(name)
            end
        )
    end

    return success, data
end

function util.wrapForInvoke(func)
    return function(...)
        local first = ...
        local args = {}
        if first == mw:getCurrentFrame() then
            for k, v in pairs(first.args) do
                args[k] = v
            end
            if first.args.fromParent then
                for k, v in pairs(first:getParent().args) do
                    args[k] = v
                end
            end
        else
            args = {...}
        end
        return func(unpack(args))
    end
end

function util.minetext(args)
    local text = util.table_print(args or {{1}})
    if type(args) == "string" then
        text = args
    else
        args = args.args or args
        text = args[1]
    end
    local blocktab = {
        __call = function(left, right)
            left.t[#left.t + 1] = right
        end,
        __index = {
            reset = function(this)
                this.fmt = {l = false, m = false, n = false, o = false}
                this.c = false
            end,
            classes = function(this)
                local c = {}
                if this.c then
                    c = {"format-" .. tostring(this.c, 16)}
                end
                for k, v in pairs(this.fmt) do
                    if v then
                        c[#c + 1] = "format-" .. k
                    end
                end
                return table.concat(c, " ")
            end,
            text = function(this)
                return table.concat(this.t, "")
            end
        }
    }
    local newblock = function(oldblock)
        b = {
            c = false,
            t = {}
        }
        setmetatable(b, blocktab)
        b:reset()
        if oldblock then
            for k, v in pairs(oldblock.fmt) do
                b.fmt[k] = v
            end
            b.c = oldblock.c
        end
        return b
    end

    local escapes = {["#"] = "&#23;"}
    for i, v in ipairs({"\\", "/", "&", "<", ">"}) do
        escapes[v] = v
    end
    setmetatable(
        escapes,
        {__index = function(tab, key)
                return "\\" + key
            end}
    )

    local currblock = newblock()
    local blocks = {currblock}
    local state = "text" -- text, escape, format
    for w in text:gmatch(".") do
        if state == "escape" then
            currblock(escapes[w])
            state = "text"
        elseif state == "text" then
            if w == "\\" then
                state = "escape"
            elseif w == "&" then
                state = "format"
            else
                currblock(w)
            end
        elseif state == "format" then
            if w:match("[0-9a-f]") then
                currblock = newblock()
                blocks[#blocks + 1] = currblock
                currblock.c = tonumber(w, 16)
            elseif w:match("[l-o]") then
                currblock = newblock(currblock)
                blocks[#blocks + 1] = currblock
                currblock.fmt[w] = not currblock.fmt[w]
            else
                currblock("&" .. w)
            end
            state = "text"
        end
    end

    local outblocks = {}
    for i, block in ipairs(blocks) do
        local b = mw.html.create("span"):attr("class", block:classes()):wikitext(block:text())
        outblocks[#outblocks + 1] = b
    end

    if args.nowrap then
        local strings = {}
        for i, block in ipairs(outblocks) do
            strings[#strings + 1] = tostring(block)
        end
        return table.concat(strings, "")
    else
        local out = mw.html.create("span"):addClass("craftingGridText")
        for i, block in ipairs(outblocks) do
            out:node(block)
        end
        return tostring(out)
    end
end

function util.join(frame)
    local out = {}
    for i, s in ipairs(frame.args) do
        if not mw.ustring.match(s, "^%s*$") then
            out[#out + 1] = s
        end
    end
    return table.concat(out, frame.args.sep)
end

-- This function is CASE-senstive.
-- The title will be parsed and the first letter of the root page
-- will be capitalized automatically.
function util.hasSubPage(f)
    local args = f or {}
    if args == mw.getCurrentFrame() then
        args = f.args or f:getParent().args
    end
    -- Trim whitespace
    args = require( [[Module:ProcessArgs]] ).norm(args)

    -- If we are on a language page, account for that
    local title = mw.title.new(args[1] or "") or {}
    local subpage = args[2]
    local result = nil
    if code(title) then
    	local frame = mw.getCurrentFrame()
    	local args = {title.fullText, 1, -2}
    	result = frame:callParserFunction("#titleparts", args) == subpage
    else
        result = title.subpageText == subpage
    end
    
    return result and 'true' or ''
end

return util