Feed The Beast Wiki

Follow the Feed The Beast Wiki on Discord or Mastodon!

READ MORE

Feed The Beast Wiki
mNo edit summary
(Cleanup return value)
 
(37 intermediate revisions by 7 users not shown)
Line 1: Line 1:
local p = {}
+
local util = {}
   
  +
local type, pairs, ipairs = type, pairs, ipairs
local util = require("Module:Utility_functions")
 
   
  +
function util.table_print(tt, indent, done)
-- returns (true, module) or (false, message)
 
  +
done = done or {}
function loadTranslatedModule(name, forceUntranslated)
 
local moduleName = name
+
indent = indent or 0
if not forceUntranslated then
+
if type(tt) == "table" then
moduleName = moduleName .. util.pageSuffix()
+
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
return pcall(function()
 
return require(moduleName)
 
end)
 
 
end
 
end
   
  +
function util.to_string(tbl)
local stringsLoaded, strings = loadTranslatedModule("Module:Infobox/strings")
 
if not stringsLoaded then
+
if "nil" == type(tbl) then
  +
return tostring("nil")
strings = {}
 
  +
elseif "string" == type(tbl) then
  +
return '"' .. tostring(tbl) .. '"'
  +
elseif "table" == type(tbl) then
  +
return util.table_print(tbl)
  +
else
  +
return tostring(tbl)
  +
end
 
end
 
end
  +
setmetatable(strings, {__index = function(tab, key)
 
  +
function util.trim(s)
if (key == "_fellbackStrings") then
 
  +
-- from PiL2 20.4
tab._fellbackStrings = {}
 
return tab._fellbackStrings
+
return (s:gsub("^%s*(.-)%s*$", "%1"))
  +
end
  +
  +
local function __genOrderedIndex(t)
  +
local orderedIndex = {}
  +
for key in pairs(t) do
  +
table.insert(orderedIndex, key)
 
end
 
end
+
table.sort(
  +
orderedIndex,
if (key == "_missingStrings") then
 
tab._missingStrings = {}
+
function(left, right)
  +
if type(left) == "table" then
return tab._missingStrings
 
  +
left = left[1]
end
 
+
end
if key == "_ModuleStringTableProblem" then
+
if type(right) == "table" then
  +
right = right[1]
if rawget(tab,"_stringTableEntirelyMissing") then
 
  +
end
return "!!! [[Module:Infobox/strings]] is gone or not a valid Lua module !!!"
 
end
+
return left < right
if (#tab._fellbackStrings > 0) or (#tab._missingStrings > 0) then
 
return "!!! Problem with [[Module:Infobox/strings]] or [[Module:Infobox/strings".. util.pageSuffix() .."|a translation thereof]]. Fallbacks used for: {" .. table.concat(tab._fellbackStrings, ", ") .. "}. Missing strings: {" .. table.concat(tab._missingStrings, ", ") .. "}. Please inform a translation admin as the translation markings probably need updating. !!!"
 
else
 
return nil
 
 
end
 
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
 
end
  +
-- fetch the next value
 
  +
key = nil
if not rawget(tab,"_fallback") then
 
pcall(function()
+
for i = 1, #t.__orderedIndex do
  +
if t.__orderedIndex[i] == state then
tab._fallback = require("Module:Infobox/strings")
 
  +
key = t.__orderedIndex[i + 1]
end)
 
if not rawget(tab,"_fallback") then
 
tab._stringTableEntirelyMissing = true
 
tab._fallback = { }
 
 
end
 
end
 
end
 
end
  +
 
  +
if key then
local val = tab._fallback[key]
 
  +
return key, t[key]
if val then
 
tab._fellbackStrings[#(tab._fellbackStrings)+1] = key
 
else
 
tab._missingStrings[#(tab._missingStrings)+1] = key
 
val = "noString!"..key
 
 
end
 
end
val = string.gsub(val, "</?translate>","")
 
tab[key] = val -- inhibit repeats.
 
return val
 
end})
 
   
  +
-- no more value to return, cleanup
local function makePreprocess(frame)
 
  +
t.__orderedIndex = nil
return function(str)
 
if str then
+
return
return frame:preprocess(tostring(str)) or "wat"
 
else
 
return ""
 
end
 
end
 
 
end
 
end
   
  +
function util.orderedPairs(t)
-- load an infobox
 
  +
-- Equivalent of the pairs() function on tables. Allows to iterate
local function loadInfobox(name, forceUntranslated)
 
  +
-- in order
return loadTranslatedModule("Module:Infobox/" .. util.trim(name), forceUntranslated)
 
  +
return orderedNext, t, nil
 
end
 
end
   
  +
local langnames = nil
-- this is exactly as lua-users wiki defined it. Never mind the odd gsub argument.
 
local function interp(s, tab)
+
local code = function(title)
  +
local subPage = (title or mw.title.getCurrentTitle()).subpageText:lower()
return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end))
 
  +
if not langNames then
  +
langNames = require([[Module:Language/Names]])
 
end
 
end
  +
if langNames[subPage] then
 
  +
return subPage
-- return the canonical, ie first, name given in an argdata.
 
local function argCName(argdata)
 
if type(argdata.arg) == "string" then
 
return argdata.arg
 
else
 
return argdata.arg[1]
 
 
end
 
end
 
end
 
end
  +
function util.pageSuffix()
 
  +
local langCode = code()
-- wrap an argument value in the necessary surround
 
  +
if langCode then
local function wrapArg(argdata, value)
 
if argdata.type == "image" then
+
return "/" .. langCode
local sizeRequested = tonumber(argdata.maxSize or "0")
 
if sizeRequested == 0 then
 
sizeRequested = tonumber(strings.maxImageSize)
 
end
 
 
local parameters = (argdata.imageParameters or strings.imageParameters)
 
if parameters == "" then
 
parameters = nil
 
end
 
mw.log({'File:'.. value, sizeRequested .. 'px', parameters})
 
 
-- force frameless for now, because that makes size do the right thing
 
local stuff = util.compact({'File:'.. value, sizeRequested .. 'px', parameters , "frameless"})
 
return '[['.. table.concat(stuff,"|")..']]'
 
else
 
return value
 
 
end
 
end
  +
return "/en"
 
end
 
end
   
  +
function util.compact(tab)
-- resolve argument data for a given arg table
 
  +
local keys = {}
local function argtableResolver(argdata, argtable, errordata)
 
if not argdata then
+
local n = 0
return nil
+
for k, v in pairs(tab) do
  +
n = n + 1
  +
keys[n] = k
 
end
 
end
+
local out = {}
  +
n = 0
if type(argdata) == "string" then
 
return argdata
+
for k, v in ipairs(keys) do
  +
n = n + 1
  +
out[n] = tab[v]
 
end
 
end
  +
return out
  +
end
   
  +
-- this is exactly as lua-users wiki defined it. Never mind the odd gsub argument.
local text = nil
 
  +
function util.interp(s, tab)
if type(argdata.arg) == "string" then
 
  +
return (s:gsub(
text = argtable[argdata.arg]
 
  +
"($%b{})",
elseif (type(argdata.arg) == "table") and (#(argdata.arg) > 0) then
 
for i,arg in ipairs(argdata.arg) do
+
function(w)
local candidate = util.trim(argtable[arg] or "")
+
return tab[w:sub(3, -2)] or w
if not (candidate == "") then
 
text = candidate
 
end
 
 
end
 
end
else
+
))
error(strings.errArgMissingArgName .. " " .. interp(position.formatString, errordata))
 
end
 
 
if not text then
 
return nil
 
end
 
 
if argdata.type == "switch" then
 
if (not argdata.allowedValues) or (type(argdata.allowedValues) ~= "table") then
 
error(strings.errArgMissingSwitchValues .. " " .. interp(position.formatString, errordata))
 
elseif not argdata.allowedValues[text] then
 
text = strings.unknownType .. ' [[Category:Articles with bad parameter values]]'
 
else
 
text = argdata.allowedValues[text]
 
end
 
end
 
 
if argdata.style then
 
return {style=argdata.style, wrapArg(argdata, text)}
 
else
 
return wrapArg(argdata, text)
 
end
 
 
end
 
end
   
local function makeResolver(frame)
+
local function argOr(name, default)
  +
local frame = mw.getCurrentFrame()
sourceframe = frame
 
if frame.args.fromParent then
+
if frame == nil then
sourceframe = frame:getParent()
+
return default
end
 
return function(argdata, errordata)
 
return argtableResolver(argdata, sourceframe.args, errordata)
 
 
end
 
end
  +
return frame.args[name]
 
end
 
end
   
  +
-- returns (true, module) or (false, message)
-- resolve argument data for an empty infobox
 
  +
function util.requireDataLocalized(name)
-- parameters
 
  +
-- So many ors because tilesheets somehow calls functions that call this *without a frame*
-- argdata: argdata|string
 
  +
local forceUntranslated = argOr("forceUntranslated", false)
-- returns
 
  +
local moduleName = name
-- string|{style=cellstyle, string}
 
  +
if not forceUntranslated then
local function emptyBoxResolver(argdata)
 
  +
moduleName = moduleName .. util.pageSuffix()
if not argdata then
 
return "wat"
 
 
end
 
end
  +
 
  +
local success, data =
if type(argdata) == "string" then
 
return argdata
+
pcall(
  +
function()
elseif type(argdata) == "table" then
 
local text = "{{{"..argCName(argdata).."}}}"
+
return mw.loadData(moduleName)
if argdata.style then
 
return {style=argdata.style, wrapArg(argdata, text)}
 
else
 
return wrapArg(argdata, text)
 
 
end
 
end
  +
)
  +
  +
if not success then
  +
success, data =
  +
pcall(
  +
function()
  +
return mw.loadData(name)
  +
end
  +
)
 
end
 
end
  +
  +
return success, data
 
end
 
end
   
  +
function util.wrapForInvoke(func)
-- resolve the arguments of an infobox definition according to the given resolver,
 
  +
return function(...)
-- and return a structure with all the arguments resolved and any unnecessary sections
 
  +
local first = ...
-- etc omitted (sections with an argument title are never omitted).
 
  +
local args = {}
-- parameters
 
  +
if first == mw:getCurrentFrame() then
-- args.box: infobox definition
 
  +
for k, v in pairs(first.args) do
-- args.resolver: function(argdata|string) -> string.
 
  +
args[k] = v
local function calculateEffectiveInfobox(args)
 
local effectiveBox = {type="box"}
 
 
if (not args.box) or (type(args.box) ~= "table") then
 
error(strings.errNoFormat)
 
end
 
 
local sectioncount = 0
 
for i, section in ipairs(args.box) do
 
if type(section) ~= "table" then
 
error(interp(strings.errBadSection, {section = i}))
 
end
 
 
if not section.docOnly then
 
sectioncount = sectioncount + 1
 
 
if not section.title then
 
error(interp(strings.errSectionNoTitle, {section = i}))
 
 
end
 
end
+
if first.args.fromParent then
  +
for k, v in pairs(first:getParent().args) do
local newSection = { title = args.resolver(section.title, {formatString = strings.errSectionLocation, section = i}), type="section" }
 
+
args[k] = v
for j, subsection in ipairs(section) do
 
if type(subsection) ~= "table" then
 
error(interp(strings.errBadSubsection, {section = i, subsection = j}))
 
end
 
if not subsection.docOnly then
 
local newSubsection = {type="subsection"}
 
 
for k, row in ipairs(subsection) do
 
if type(row) ~= "table" then
 
error(interp(strings.errBadRow, {section = i, subsection = j, row = k}))
 
end
 
if not row.docOnly then
 
local newRow = { type="row"}
 
 
for m, cell in ipairs(row) do
 
newRow[#newRow + 1] = args.resolver(cell, {formatString = strings.errCellLocation, section = i, subsection = j, row = k, cell = m})
 
end
 
 
if (#row == #newRow) then
 
newSubsection[#newSubsection+1] = newRow
 
end
 
end
 
end
 
 
if (#newSubsection > 0) then
 
newSection[#newSection + 1] = newSubsection
 
end
 
 
end
 
end
 
end
 
end
+
else
  +
args = {...}
if (#newSection > 0) or (type(section.title) == "table") then
 
effectiveBox[#effectiveBox + 1] = newSection
 
end
 
 
end
 
end
  +
return func(unpack(args))
 
end
 
end
 
if sectioncount == 0 then
 
error(strings.errEmptyInfoboxFormat)
 
end
 
 
return effectiveBox
 
 
end
 
end
   
  +
function util.minetext(args)
-- convert an infobox definition that has had all the arguments resolved, to a string.
 
  +
local text = util.table_print(args or {{1}})
-- box like
 
  +
if type(args) == "string" then
-- { class=tableclass, { title=sectionheader, { { cell, cell }, {cell, cell } } } }
 
  +
text = args
-- cell like string or { style=cellstyle, content }
 
  +
else
local function renderBox(box, wtprocessor)
 
result = ""
+
args = args.args or args
local out = function(thing)
+
text = args[1]
result = result .. thing
 
 
end
 
end
  +
local blocktab = {
out('<table class="infobox')
 
  +
__call = function(left, right)
if box.class then
 
out(' '..box.class)
+
left.t[#left.t + 1] = right
end
+
end,
out('">')
+
__index = {
  +
reset = function(this)
 
  +
this.fmt = {l = false, m = false, n = false, o = false}
local count = 0
 
  +
this.c = false
 
  +
end,
for i,section in ipairs(box) do
 
if i == 1 then
+
classes = function(this)
out('<tr><th class="infoboxFirstHeader" colspan="2">')
+
local c = {}
else
+
if this.c then
  +
c = {"format-" .. tostring(this.c, 16)}
out('<tr class="infoboxSectionHeader"><th class="infoboxSectionHeader" colspan="2">')
 
end
+
end
local title = wtprocessor(section.title)
+
for k, v in pairs(this.fmt) do
out(title)
+
if v then
  +
c[#c + 1] = "format-" .. k
out('</th></tr>')
 
 
if title and (title ~= "") then
 
count = count + 1
 
end
 
 
for j,subsection in ipairs(section) do
 
if j > 1 then
 
out('<tr class="infoboxSubsectionBreak"><td></td><td></td></tr>')
 
end
 
 
for k, row in ipairs(subsection) do
 
out('<tr>')
 
for m,cell in ipairs(row) do
 
out('<td')
 
if #row == 1 then
 
out(' colspan="2"')
 
 
end
 
end
if type(cell)=="table" and cell.style then
 
out(' style="'..cell.style..'"')
 
end
 
out('>')
 
if type(cell)=="string" then
 
out(wtprocessor(cell))
 
elseif type(cell)=="table" then
 
out(wtprocessor(cell[1]))
 
end
 
count = count + 1
 
out('</td>')
 
 
end
 
end
out('</tr>')
+
return table.concat(c, " ")
  +
end,
  +
text = function(this)
  +
return table.concat(this.t, "")
 
end
 
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
 
end
  +
return b
 
end
 
end
  +
 
if count == 0 then
+
local escapes = {["#"] = "&#23;"}
  +
for i, v in ipairs({"\\", "/", "&", "<", ">"}) do
out('<tr><td colspan="2">' .. strings.errNoOutput .. '</td></tr>')
 
  +
escapes[v] = v
 
end
 
end
+
setmetatable(
out('</table>')
+
escapes,
  +
{__index = function(tab, key)
 
return result
+
return "\\" + key
  +
end}
end
 
  +
)
 
local function renderEmptyBox(frame, box)
 
local presentedBox = calculateEffectiveInfobox{box=box.format, resolver = emptyBoxResolver}
 
presentedBox.class="infoboxNoCollapse"
 
 
return renderBox(presentedBox, function(str)
 
return frame:preprocess(str or "")
 
end)
 
end
 
   
  +
local currblock = newblock()
function p.calculateParameterListing(box)
 
local parameters = {}
+
local blocks = {currblock}
  +
local state = "text" -- text, escape, format
for i,section in ipairs(box) do
 
if not section.noDoc then
+
for w in text:gmatch(".") do
local sectiondata = { name = section.name}
+
if state == "escape" then
  +
currblock(escapes[w])
if (not parameters[#parameters]) or (not (sectiondata.name == parameters[#parameters].name)) then
 
parameters[#parameters+1] = sectiondata
+
state = "text"
  +
elseif state == "text" then
  +
if w == "\\" then
  +
state = "escape"
  +
elseif w == "&" then
  +
state = "format"
 
else
 
else
sectiondata = parameters[#parameters]
+
currblock(w)
 
end
 
end
+
elseif state == "format" then
if type(section.title) == "table" then
+
if w:match("[0-9a-f]") then
sectiondata[#sectiondata+1] = section.title
+
currblock = newblock()
end
+
blocks[#blocks + 1] = currblock
  +
currblock.c = tonumber(w, 16)
 
if section.desc then
+
elseif w:match("[l-o]") then
sectiondata[#sectiondata+1] = { text = section.desc }
+
currblock = newblock(currblock)
end
+
blocks[#blocks + 1] = currblock
  +
currblock.fmt[w] = not currblock.fmt[w]
 
for j,subsection in ipairs(section) do
+
else
if not subsection.noDoc then
+
currblock("&" .. w)
if subsection.desc then
 
sectiondata[#sectiondata+1] = { text = subsection.desc }
 
end
 
for k,row in ipairs(subsection) do
 
if not subsection.noDoc then
 
if row.desc then
 
sectiondata[#sectiondata+1] = { text = row.desc }
 
end
 
for m,cell in ipairs(row) do
 
if (type(cell) == "table") and (cell.desc) and (not cell.noDoc) then
 
sectiondata[#sectiondata+1] = cell
 
end
 
end
 
end
 
end
 
end
 
 
end
 
end
  +
state = "text"
 
end
 
end
 
end
 
end
return parameters
 
end
 
   
  +
local outblocks = {}
local function renderParameterListing(box)
 
  +
for i, block in ipairs(blocks) do
local parameters = p.calculateParameterListing(box)
 
  +
local b = mw.html.create("span"):attr("class", block:classes()):wikitext(block:text())
 
  +
outblocks[#outblocks + 1] = b
local collapseByValue = function(tab)
 
local iv={}
 
for k,v in pairs(tab) do
 
if not iv[v] then
 
iv[v]={k}
 
else
 
iv[v][#(iv[v]) + 1] = k
 
end
 
end
 
local s={}
 
for k,v in pairs(iv) do
 
if not s[v] then
 
s[v]=k
 
end
 
end
 
return s
 
 
end
 
end
   
result = ''
+
if args.nowrap then
  +
local strings = {}
for i,section in ipairs(parameters) do
 
  +
for i, block in ipairs(outblocks) do
if section.name then
 
result = result .. "===" .. section.name .. "===\n"
+
strings[#strings + 1] = tostring(block)
 
end
 
end
  +
return table.concat(strings, "")
for j,parameter in ipairs(section) do
 
  +
else
if parameter.text then
 
  +
local out = mw.html.create("span"):addClass("craftingGridText")
result = result .. parameter.text .. '\n'
 
elseif parameter.desc then
+
for i, block in ipairs(outblocks) do
local namelist = parameter.arg
+
out:node(block)
if type(parameter.arg) == "table" then
 
namelist = table.concat(parameter.arg, "''' ''" .. strings.nameorname .. "'' '''")
 
end
 
result = result .. "* '''" .. namelist .. "''': " .. parameter.desc
 
if parameter.type == "switch" then
 
result = result .. " " .. strings.switchdoc .."\n"
 
for key,value in util.orderedPairs(collapseByValue(parameter.allowedValues)) do
 
result = result .. "** ''" .. table.concat(key, "''" .. strings.nameorname .. "''") .. "'': " .. value .. "\n"
 
end
 
elseif parameter.type == "image" then
 
result = result .. " " .. strings.imagedoc .. "\n"
 
else
 
result = result .. "\n"
 
end
 
end
 
 
end
 
end
  +
return tostring(out)
 
end
 
end
return result
 
 
end
 
end
   
local function makeInvokeWithBox(func)
+
function util.join(frame)
  +
local out = {}
return function(frame)
 
local out = ""
+
for i, s in ipairs(frame.args) do
  +
if not mw.ustring.match(s, "^%s*$") then
local success, box = loadInfobox(frame.args[1], frame.args.forceUntranslated)
 
if not success then
+
out[#out + 1] = s
out = "!!! " .. strings.loadFailure .. " [[Module:Infobox/" .. frame.args[1] .. util.pageSuffix() .. "]] " .. strings.informTranslationAdmin .. " \n\n"
 
out = out .. box .. " !!!\n\n"
 
else
 
local success = nil
 
local result = nil
 
if frame.args.stackTrace then
 
success, result = xpcall(function()
 
func(frame, box)
 
end, debug.traceback)
 
else
 
success, result = pcall(func, frame, box)
 
end
 
 
if not success then
 
out = "!!! " .. result .. " " .. strings.informTranslationAdmin .. " !!!\n\n"
 
else
 
out = result
 
end
 
end
 
 
if strings._ModuleStringTableProblem then
 
out = strings._ModuleStringTableProblem .. "\n\n" .. out
 
 
end
 
end
 
return out
 
 
end
 
end
  +
return table.concat(out, frame.args.sep)
 
end
 
end
   
  +
-- This function is CASE-senstive.
-- emit an infobox.
 
  +
-- The title will be parsed and the first letter of the root page
-- parameters:
 
  +
-- will be capitalized automatically.
-- 1: Infobox definition to use
 
  +
function util.hasSubPage(f)
-- everything else: whatever the infobox definition says
 
  +
local args = f or {}
 
  +
if args == mw.getCurrentFrame() then
p.infobox = makeInvokeWithBox(function(frame, box)
 
  +
args = f.args or f:getParent().args
local presentedBox = calculateEffectiveInfobox{box=box.format, resolver = makeResolver(frame)}
 
 
local out = renderBox(presentedBox, function(str)
 
return frame:preprocess(str or "")
 
end)
 
 
if (frame.args.class) or frame:getParent().args.class then
 
out = out .. " [[Category:Articles that pass class to module infobox]] "
 
 
end
 
end
  +
-- Trim whitespace
 
  +
args = require( [[Module:ProcessArgs]] ).norm(args)
return out
 
end)
 
   
  +
-- If we are on a language page, account for that
-- emit an infobox with the parameters displayed
 
  +
local title = mw.title.new(args[1] or "") or {}
-- parameters:
 
  +
local subpage = args[2]
-- 1: Infobox definition to use
 
  +
local result = nil
p.emptyBox = makeInvokeWithBox(function(frame, box)
 
  +
if code(title) then
return renderEmptyBox(frame, box)
 
  +
local frame = mw.getCurrentFrame()
end)
 
  +
local args = {title.fullText, 1, -2}
 
  +
result = frame:callParserFunction("#titleparts", args) == subpage
-- emit the parameter list for documentation
 
-- parameters
 
-- 1: Infobox definition to use
 
p.parameterHelp = makeInvokeWithBox(function(frame, box)
 
return renderParameterListing(box.format)
 
end)
 
 
-- emit the entire documentation page
 
-- parameters
 
-- 1: infobox definition to use
 
-- in the infobox definition
 
-- intro is the text at the top
 
-- postParameterText is displayed after the parameter listing
 
-- examples is an array of examples: { { optional text = wikitext string, template-supporting wikitext}, ... }
 
p.documentationPage = makeInvokeWithBox(function(frame, box)
 
local fp = makePreprocess(frame)
 
 
local function hatnote(str)
 
return frame:expandTemplate{title = "Hatnote", args = {str}}
 
end
 
 
out = frame:expandTemplate{title = "Doc/Start"}
 
 
local noteText = interp(strings.generatedFrom, {template = "Module:Infobox/" .. frame.args[1]})
 
 
out = out .. hatnote(fp(noteText)) .. '\n'
 
out = out .. fp(strings.docLead) .. ' '
 
out = out .. fp(box.docLead) .. '\n\n'
 
out = out .. "==" .. strings.parameters .. "==\n"
 
out = out .. fp(strings.parameterLead) .. " "
 
out = out .. fp(box.parameterLead)
 
out = out .. renderEmptyBox(frame, box) .. '\n'
 
out = out .. renderParameterListing(box.format)
 
out = out .. '\n' .. fp(box.parameterTrail)
 
 
out = out .. '<div style="clear:both;"></div>'
 
 
out = out .. "\n==" .. strings.examples .. "==\n"
 
if box.examples then
 
for i, example in ipairs(box.examples) do
 
out = out .. fp(example.text) .. '\n'
 
out = out .. '<table class="wikitable">\n<tr><th>' .. strings.exampleCode .. '</th><th>' .. strings.exampleResult .. '</th></tr>\n'
 
out = out .. '<tr>\n<td><pre>\n' .. example[1] .. '\n</pre></td>\n'
 
out = out .. '<td>\n' .. fp(example[1]) .. '\n</td></tr>\n'
 
out = out .. '</table>\n\n'
 
end
 
 
else
 
else
  +
result = title.subpageText == subpage
out = out .. hatnote(fp(strings.noExamples))
 
 
end
 
end
 
 
  +
return result and 'true' or ''
out = out .. frame:expandTemplate{title = "Doc/End"}
 
  +
end
return out
 
end)
 
   
return p
+
return util

Latest revision as of 05:19, 26 August 2020

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