Module:Utility functions

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,table.getn(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 ) 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

-- Separates the provided string value representing a number by commas. Examples: -- 100 -> 100 -- 1000 -> 1,000 -- -1000 -> -1,000 -- -1000.1204 -> -1,000.1204 -- This expects the provided value to already be a string. function util.separate_number_by_commas(str) local _, _, neg_sign, int_val, decimal = str:find("([-]?)(%d+)([.]?%d*)") return neg_sign .. int_val:reverse:gsub("(%d%d%d)", "%1,"):reverse:gsub("^,", "") .. decimal 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