Feed The Beast Wiki

Follow the Feed The Beast Wiki on Discord or Mastodon!

READ MORE

Feed The Beast Wiki
Register
Advertisement

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
Advertisement