Module:Utility functions

(tagline)
(jumpto) (jumptonavigation)(comma-separator) (jumptosearch)

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 = mw.loadData( [[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.requireDataLocalised(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

return util