Module:Infobox

local p = {}

local util = require("Module:Utility_functions")

Accumulator = {} p.a = Accumulator

Accumulator.new = function local nextidx = 1 this = {} local n = { out = function(str) if not str then return end this[nextidx] = str nextidx = nextidx + 1 end, outv = function(...) for i,t in ipairs({...}) do               this[nextidx] = t                nextidx = nextidx + 1 end end, outt = function(tab) for i,t in ipairs(tab) do               this[nextidx] = t                nextidx = nextidx + 1 end end }   setmetatable(n, {__tostring = function        return table.concat(this)    end}) return n end

-- returns (true, module) or (false, message) function loadTranslatedModule(name, forceUntranslated) local moduleName = name if not forceUntranslated then moduleName = moduleName .. util.pageSuffix end return pcall(function       return require(moduleName)    end) end

local stringsLoaded, strings = loadTranslatedModule("Module:Infobox/strings") if not stringsLoaded then strings = {} end setmetatable(strings, {__index = function(tab, key)   if (key == "_fellbackStrings") then        tab._fellbackStrings = {}        return tab._fellbackStrings    end    if (key == "_missingStrings") then        tab._missingStrings = {}        return tab._missingStrings    end    if key == "_ModuleStringTableProblem" then        if rawget(tab,"_stringTableEntirelyMissing") then            return "!!! Module:Infobox/strings is gone or not a valid Lua module !!!"        end        if (#tab._fellbackStrings > 0) or (#tab._missingStrings > 0) then            return "!!! Problem with Module:Infobox/strings or 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    if not rawget(tab,"_fallback") then        pcall(function            tab._fallback = require("Module:Infobox/strings")        end)        if not rawget(tab,"_fallback") then            tab._stringTableEntirelyMissing = true            tab._fallback = { }        end    end    local val = tab._fallback[key]    if val then        tab._fellbackStrings[#(tab._fellbackStrings)+1] = key    else        tab._missingStrings[#(tab._missingStrings)+1] = key        val = "noString!"..key    end    val = string.gsub(val, "","")    tab[key] = val -- inhibit repeats.    return val end})

local function makePreprocess(frame) return function(str) if str then return frame:preprocess(tostring(str)) or "wat" else return "" end end end

-- load an infobox local function loadInfobox(name, forceUntranslated) return loadTranslatedModule("Module:Infobox/" .. util.trim(name), forceUntranslated) end

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

-- 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

-- wrap an argument value in the necessary surround local function wrapArg(argdata, value) if argdata.type == "image" then 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 '")..'' elseif argdata.type == "link" then local linktext = (argdata.linkText or strings.linkText) return table.concat({'[', value, ' ', linktext, ']'}) else return value end end

-- resolve argument data for a given arg table local function argtableResolver(argdata, argtable, errordata) if not argdata then return nil end if type(argdata) == "string" then return argdata end

local text = nil if type(argdata.arg) == "string" then text = argtable[argdata.arg] elseif (type(argdata.arg) == "table") and (#(argdata.arg) > 0) then for i,arg in ipairs(argdata.arg) do           local candidate = util.trim(argtable[arg] or "") if not (candidate == "") then text = candidate 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 .. ' '       else text = argdata.allowedValues[text] end end

if argdata.units then text = text .. " " .. argdata.units end if argdata.style then return {style=argdata.style, wrapArg(argdata, text)} else return wrapArg(argdata, text) end end

local function makeResolver(frame) sourceframe = frame if frame.args.fromParent then sourceframe = frame:getParent end return function(argdata, errordata) return argtableResolver(argdata, sourceframe.args, errordata) end end

-- resolve argument data for an empty infobox -- parameters --  argdata: argdata|string -- returns --  string|{style=cellstyle, string} local function emptyBoxResolver(argdata) if not argdata then return "wat" end if type(argdata) == "string" then return argdata elseif type(argdata) == "table" then local text = "" if argdata.style then return {style=argdata.style, wrapArg(argdata, text)} else return wrapArg(argdata, text) end end end

-- resolve the arguments of an infobox definition according to the given resolver, -- and return a structure with all the arguments resolved and any unnecessary sections -- etc omitted (sections with an argument title are never omitted). -- parameters --  args.box: infobox definition --  args.resolver: function(argdata|string) -> string. 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 local newSection = { title = args.resolver(section.title, {formatString = strings.errSectionLocation, section = i}), type="section" } 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 if (#newSection > 0) or (type(section.title) == "table") then effectiveBox[#effectiveBox + 1] = newSection end end end if sectioncount == 0 then error(strings.errEmptyInfoboxFormat) end return effectiveBox end

-- convert an infobox definition that has had all the arguments resolved, to a string. -- box like -- { class=tableclass, { title=sectionheader, { { cell, cell }, {cell, cell } } } } -- cell like string or { style=cellstyle, content } local function renderBox(box, wtprocessor) result = "" local out = function(thing) result = result .. thing end out(' ') return result 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

--- returns list of { name = string, text = string, argdata... } function p.calculateParameterListing(box, nondocmode) local parameters = {} for i,section in ipairs(box) do       if (nondocmode and (not section.docOnly)) or (not section.noDoc) then local sectiondata = { name = section.name} if (not parameters[#parameters]) or (not (sectiondata.name == parameters[#parameters].name)) then parameters[#parameters+1] = sectiondata else sectiondata = parameters[#parameters] end if type(section.title) == "table" then sectiondata[#sectiondata+1] = section.title end if section.desc then sectiondata[#sectiondata+1] = { text = section.desc } end

for j,subsection in ipairs(section) do               if (nondocmode and (not subsection.docOnly)) or (not subsection.noDoc) then if subsection.desc then sectiondata[#sectiondata+1] = { text = subsection.desc } end for k,row in ipairs(subsection) do                       if (nondocmode and (not row.docOnly)) or (not row.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 ((nondocmode and (not cell.docOnly)) or (cell.desc and (not cell.noDoc))) then sectiondata[#sectiondata+1] = cell end end end end end end end end return parameters end

local function renderParameterListing(box, a)   local parameters = p.calculateParameterListing(box) 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 local out = a.out local outt = a.outt

for i,section in ipairs(parameters) do       if section.name then outt{"===",section.name,"===\n"} end for j,parameter in ipairs(section) do           if parameter.text then outt{parameter.text,'\n'} elseif parameter.desc then local namelist = parameter.arg if type(parameter.arg) == "table" then namelist = table.concat(parameter.arg, "  " .. strings.nameorname .. "  ") end outt{"* ",namelist,": ",parameter.desc} if parameter.type == "switch" then outt{" ",strings.switchdoc,"\n"} for key,value in util.orderedPairs(collapseByValue(parameter.allowedValues)) do                       outt{"** ",table.concat(key, "  " .. strings.nameorname .. "  "),": ",value,"\n"} end elseif parameter.type == "image" then outt{" ",strings.imagedoc ,"\n"} else out("\n") end end end end return result end

local function makeInvokeWithBox(func) return function(frame) local out = "" local success, box = loadInfobox(frame.args[1], frame.args.forceUntranslated) if not success then 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 return out end end

-- emit an infobox. -- parameters: --  1: Infobox definition to use --  everything else: whatever the infobox definition says

p.infobox = makeInvokeWithBox(function(frame, box)   local presentedBox = calculateEffectiveInfobox{box=box.format, resolver = makeResolver(frame)}    local out = renderBox(presentedBox, function(str) return frame:preprocess(str or "") end)   local paramList = p.calculateParameterListing(box.format, true)    local params = {}

for i,section in ipairs(paramList) do       for j, param in ipairs(section) do            if type(param.arg) == "table" then for j,a in ipairs(param.arg) do                   params[a] = true end elseif param.arg then params[param.arg] = true end end end

local hasBadArgs = false for name, value in pairs((frame:getParent or frame).args) do       if not params[name] then hasBadArgs = true out = out .. "\n\n" .. strings.badParameterName .. " " .. name .. " "       end end if hasBadArgs then out = out .. ""   end return out end)

-- emit an infobox with the parameters displayed -- parameters: --  1: Infobox definition to use p.emptyBox = makeInvokeWithBox(function(frame, box)   return renderEmptyBox(frame, box) end)

-- emit the parameter list for documentation -- parameters --  1: Infobox definition to use p.parameterHelp = makeInvokeWithBox(function(frame, box)   return tostring(renderParameterListing(box.format, Accumulator.new)) 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 a = Accumulator.new    local out = a.out    local outv = a.outv    local outt = a.outt

local function hatnote(str) return frame:expandTemplate{title = "Hatnote", args = {str}} end outv("\n\n")

outv(hatnote(noteText), '\n') outv(strings.docLead, ' ') outv(box.docLead, '\n\n') outv("==", strings.parameters, "==\n") outv(strings.parameterLead, " ") out(box.parameterLead) outv(renderEmptyBox(frame, box), '\n') renderParameterListing(box.format, a)   outt{'\n', box.parameterTrail} out(' ') outv("\n==", strings.examples, "==\n") if box.examples then for i, example in ipairs(box.examples) do           outv(example.text or "", '\n') outv(' \n\n') end else out(hatnote(fp(strings.noExamples))) end

out("") return frame:preprocess(tostring(a)) end)

return p