Module:TemplateBox
--[[
@exports usagesample( frame ) argcount( frame ) args2table( args, onGetKey, forCustom ) paramtable( frame ) description( frame ) templatedata( frame )
]]
local p = {}
-- Helper function, not exposed function tobool(st)
if type( st ) == 'string' then return st == 'true' else return not not st end
end
-- Required to determine in which languages the interface texts without langcode are
local contentLangcode = mw.language.getContentLanguage():getCode()
-- Forward declaration
local msg, langIsInit, userLang
local messagePrefix = "templatedata-doc-"
local i18n = {}
i18n['params'] = "Template parameters"
i18n['param-name'] = "Parameter"
i18n['param-desc'] = "Description"
i18n['param-type'] = "Type"
i18n['param-default'] = "Default"
i18n['param-status'] = "Status"
i18n['param-status-optional'] = "optional"
i18n['param-status-required'] = "required"
i18n['param-status-suggested'] = "suggested"
i18n['param-status-deprecated'] = "deprecated"
i18n['param-default-empty'] = "empty"
function initLangModule()
if langIsInit then return end
--! From de:Modul:Expr; by de:User:PerfektesChaos; --! Derivative work: Rillke userLang = mw.getCurrentFrame():preprocess( '⧼lang⧽' )
msg = function( key ) -- Retrieve localized message string in content language -- Precondition: -- key -- string; message ID -- Postcondition: -- Return some message string -- Uses: -- > messagePrefix -- > i18n -- > userLang -- mw.message.new() local m = mw.message.new( messagePrefix .. key ) local r = false if m:isBlank() then r = i18n[ key ] else m:inLanguage( userLang ) r = m:plain() end if not r then r = '((('.. key .. ')))' end return r end -- msg() langIsInit = true
end
-- A "hash" / table of everything TemplateData takes -- to ease maintenance.
-- The type is automatically determined if t is omitted. -- If the type does not match or can't be converted, an error will be thrown! -- Available types (LUA-Types with exceptions): -- InterfaceText, boolean, number, selection, table, string -- selection*: - requires a selection-string of pipe-separated possibilities to be supplied -- InterfaceText*: A free-form string (no wikitext) in the content-language of the wiki, or, -- an object containing those strings keyed by language code. local paraminfoTemplate = {
description = { default = , t = 'InterfaceText', alias = 'desc' }
} local paraminfoTLParams = {
label = { default = , t = 'InterfaceText' }, required = { default = false, extract = function(pargs, number, paramVal) local req = (pargs[number .. 'stat'] == 'required') return tobool( paramVal or req ) end }, suggested = { default = false, extract = function(pargs, number, paramVal) local sugg = (pargs[number .. 'stat'] == 'suggested') return tobool( paramVal or sugg ) end }, description = { default = , t = 'InterfaceText', alias = 'd' }, deprecated = { default = false, extract = function(pargs, number, paramVal) local depr = (pargs[number .. 'stat'] == 'deprecated') return tobool( paramVal or depr ) end }, aliases = { default = , t = 'table', extract = function(pargs, number, paramVal) local key = number .. 'aliases' local tdkey = key .. '-td' local aliases = pargs[tdkey] or pargs[key] if aliases then aliases = mw.text.split( aliases, '/', true ) end return aliases end }, default = { default = , t = 'string', alias = 'def' }, type = { default = 'unknown', t = 'selection', selection = 'unknown|number|string|string/wiki-user-name|string/wiki-page-name|string/line|line|wiki-page-name|wiki-file-name|wiki-user-name|content|unbalanced-wikitext' }, inherits = { default = nil, t = 'string' } -- sets will be treated differently because we can only have a plain structure in wikitext
} local tableLayout = {
{ col = 'param-name', width = '15%', extract = function(item, renderCell, monolingual) local alias, param = , item.key local aliasTT = ''
param = '' .. param .. '
' if item.aliases then alias = aliasTT .. table.concat(item.aliases, '
' .. aliasTT) .. ''
param = table.concat({param, '
'})
end
renderCell(param, colspan)
end
}, {
col = 'param-desc',
cols = 2,
width = '65%',
extract = function(item, renderCell, monolingual)
local label = item.label or
label = monolingual(label)
local labelLen = #label
local colspan = 2 - labelLen
if labelLen > 0 then
renderCell(label)
end
renderCell(monolingual(item.description), colspan)
end
}, {
col = 'param-default',
width = '10%',
extract = function(item, renderCell, monolingual)
local def = monolingual(item.default) or
if #def == 0 then
def = '' .. msg('param-default-empty') .. ''
end
renderCell(def)
end
}, {
col = 'param-status',
width = '10%',
extract = function(item, renderCell, monolingual)
local stat = msg('param-status-optional')
if item.required then
stat = '' .. msg('param-status-required') .. ''
elseif item.deprecated then
stat = msg('param-status-deprecated')
elseif item.suggested then
stat = msg('param-status-suggested')
end
renderCell(stat)
end
}
}
-- Initialize param info -- Avoids having to add redundant information to the preceding tables function init( which )
local setDefault = function(v) if v.t == nil and v.default ~= nil then v.t = type( v.default ) end if v.selection then v.selection = '|' .. v.selection .. '|' end end for a, v in pairs( which ) do setDefault(v) end
end function initParamTables()
init( paraminfoTemplate ) init( paraminfoTLParams )
end
USAGE PART ----------------------
function p.argcount( frame )
local pargs = ( frame:getParent() or {} ).args or {} local ac = 0 for i, arg in pairs( pargs ) do if ('number' == type(i)) then ac = ac + 1 end end return ac
end
function p.usagesample( frame )
local pargs = ( frame:getParent() or {} ).args or {} local multiline = (pargs.lines == 'multi' or pargs.print == 'multi' or pargs.print == 'infobox') local align = pargs.print == 'infobox' if not pargs.lines and not pargs.print and pargs.type == 'infobox' then multiline = true align = true end local sepStart = ' |' local sepEnd = multiline and '\n' or local sep = sepEnd local subst = #(pargs.mustbesubst or ) > 0 and 'subst:' or local beforeEqual = multiline and ' ' or local equal = beforeEqual .. '= ' local templateTitle = pargs.name or local args, argName, result = {} local maxArgLen, eachArg = 0 sep = sep .. sepStart local comapareLegacyVal = function(val) return val == 'optional-' or val == 'deprecated' end local shouldShow = function(i) if comapareLegacyVal(pargs[i .. 'stat']) or comapareLegacyVal(pargs[i .. 'stat-td']) or pargs[i .. 'deprecated'] == true then return false end return true end eachArg = function(cb) for i, arg in pairs( pargs ) do if ('number' == type(i)) then argName = mw.text.trim( arg or ) if #argName == 0 then argName = tostring(i) end if shouldShow(i) then cb(argName) end end end end if align then eachArg(function( arg ) local argL = #arg maxArgLen = argL > maxArgLen and argL or maxArgLen end) end eachArg(function( arg ) local space = if align then space = (' '):rep(maxArgLen - #arg) end table.insert( args, argName .. space .. equal ) end) if #args == 0 then sep = sepEnd = sepStart = end if #templateTitle == 0 then templateTitle = mw.title.getCurrentTitle().text end result = table.concat( args, sep ) result = table.concat({ mw.text.nowiki('Template:'), subst, templateTitle, sep, result, sepEnd, '' }) if multiline then -- Preserve whitespace in front of new lines result = frame:callParserFunction{ name = '#tag', args = { 'poem', result } } end return result
end
GENERAL PART ---------------------
function p.args2table(args, onGetKey, consumer)
initParamTables() local sets, asParamArray, laxtype, processParams, processDesc if 'paramtable' == consumer then asParamArray = true processParams = true laxtype = true elseif 'templatedata' == consumer then sets = true processParams = true processDesc = true unstrip = true elseif 'description' == consumer then processDesc = true laxtype = true end -- All kind of strange stuff with the arguments is done, so play safe and make a copy local pargs = mw.clone( args ) -- Array-like table containing all parameter-numbers that were passed local templateArgs = {} -- Arguments that are localized (i.e. the user passed 1desc-en=English description of parameter one) local i18nTemplateArgs = {} -- Ensure that tables end up as array/object (esp. when they are empty) local tdata = {description="", params={}, sets={}} local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray setmetatable(tdata.sets, isArray) onGetKey = onGetKey or function( prefix, alias, param ) local key, key2, tdkey, tdkey2 key = prefix .. (alias or param) key2 = prefix .. param tdkey = key .. '-td' tdkey2 = key2 .. '-td' return tdkey, tdkey2, key, key2 end local extractData = function( pi, number ) local prefix = number or local ppv, paramVal local key1, key2, key3, key4 local paramKey, paramTable, processKey if number then paramKey = mw.text.trim( pargs[number] ) if == paramKey then paramKey = tostring( number ) end paramTable = {} if asParamArray then paramTable.key = paramKey table.insert(tdata.params, paramTable) else tdata.params[paramKey] = paramTable end end for p, info in pairs( pi ) do key1, key2, key3, key4 = onGetKey(prefix, info.alias, p) paramVal = nil processKey = function(key) if paramVal ~= nil then return end local plain, multilingual = pargs[key], i18nTemplateArgs[key] paramVal = multilingual or plain end processKey( key1 ) processKey( key2 ) processKey( key3 ) processKey( key4 ) -- Ensure presence of entry in content language ppv = pargs[key1] or pargs[key2] or pargs[key3] or pargs[key4] or info.default if 'table' == type( paramVal ) then if (nil == paramVal[contentLangcode]) then paramVal[contentLangcode] = ppv end else paramVal = ppv end
if 'function' == type( info.extract ) then if 'string' == type( paramVal ) then paramVal = mw.text.trim( paramVal ) if == paramVal then paramVal = nil end end paramVal = info.extract( pargs, number, paramVal ) end local insertValue = function() if number then paramTable[p] = paramVal else tdata[p] = paramVal end end if info.selection then if info.selection:find( paramVal, 1, true ) then insertValue() end elseif 'InterfaceText' == info.t then if ({ table=1, string=1 })[type( paramVal )] then insertValue() end else local paramType = type( paramVal ) if 'string' == info.t and 'string' == paramType then paramVal = mw.text.trim( paramVal ) if ~= paramVal then insertValue() end elseif 'boolean' == info.t then paramVal = tobool(paramVal) insertValue() elseif 'number' == info.t then paramVal = tonumber(paramVal) insertValue() elseif paramType == info.t then insertValue() elseif paramType == 'nil' then -- Do nothing elseif not laxtype and 'string' == info.t and 'table' == paramType then -- Convert multilingual object into content language string paramVal = paramVal[contentLangcode] insertValue() else if laxtype then insertValue() else error( p .. ': Is of type ' .. paramType .. ' but should be of type ' .. (info.t or 'unknown'), 1 ) end end end end -- Now, treat sets if sets then key1 = prefix .. 'set-td' key2 = prefix .. 'set' paramVal = pargs[key1] or pargs[key2] if paramVal then local found = false for i, s in ipairs( tdata.sets ) do if s.label == paramVal then table.insert( s.params, p ) found = true end end if not found then table.insert( tdata.sets, { label = paramVal, params = { p } } ) end end end end -- First, analyse the structure of the provided arguments for a, v in pairs( pargs ) do if unstrip then v = mw.text.unstrip( v ) pargs[a] = v end if type( a ) == 'number' then table.insert( templateArgs, a ) else local argSplit = mw.text.split( a, '-', true ) local argUnitl = {} local argAfter = {} local isTDArg = false local containsTD = a:find( '-td', 1, true ) for i, part in ipairs( argSplit ) do if isTDArg or (containsTD == nil and i > 1) then -- This is likely a language version table.insert( argAfter, part ) else table.insert( argUnitl, part ) end if part == 'td' then isTDArg = true end end if #argAfter > 0 then argUnitl = table.concat( argUnitl, '-' ) argAfter = table.concat( argAfter, '-' ) i18nTemplateArgs[argUnitl] = i18nTemplateArgs[argUnitl] or {} i18nTemplateArgs[argUnitl][argAfter] = v end end end -- Then, start building the actual template if processDesc then extractData( paraminfoTemplate ) end if processParams then for i, number in pairs( templateArgs ) do extractData( paraminfoTLParams, number ) end end return tdata, #templateArgs
end
CUSTOM PARAMETER TABLE PART -------------
-- A custom key-pref-function local customOnGetKey = function( prefix, alias, param )
local key, key2, tdkey, tdkey2 key = prefix .. (alias or param) key2 = prefix .. param tdkey = key .. '-td' tdkey2 = key2 .. '-td' return key2, key, tdkey2, tdkey
end local toUserLanguage = function(input, frame)
if type(input) == 'table' then input = frame:expandTemplate{ title = 'LangSwitch', args = input } end return input
end
function p.description(frame)
local pargs = ( frame:getParent() or {} ).args or {} local tdata, paramLen local monolingual = function(input) return toUserLanguage(input, frame) end tdata, paramLen = p.args2table(pargs, customOnGetKey, 'description') return monolingual(tdata.description)
end
function p.paramtable(frame)
local pargs = ( frame:getParent() or {} ).args or {} local tdata, paramLen if 'only' == pargs.useTemplateData then return 'param table - output suppressed' end -- Initialize the language-related stuff initLangModule() local monolingual = function(input) return toUserLanguage(input, frame) end
tdata, paramLen = p.args2table(pargs, customOnGetKey, 'paramtable') if 0 == paramLen then return end local row, rows = , {} local renderCell = function(wikitext, colspan)
local colspan, oTd = colspan or 1, ''
if colspan > 1 then
oTd = ''
end
row = table.concat({ row, oTd, wikitext, '' }) end -- Create the header for i, field in ipairs( tableLayout ) do local style = ' style="width:' .. field.width .. '"' local colspan = if field.cols then colspan = ' colspan="' .. field.cols .. '"' end local th = '<th' .. style .. colspan .. '>' row = row .. th .. msg(field.col) .. '' end table.insert(rows, row) -- Now transform the Lua-table into an HTML-table for i, item in ipairs( tdata.params ) do row = for i2, field in ipairs( tableLayout ) do field.extract(item, renderCell, monolingual) end table.insert(rows, row) end return '
' .. table.concat(rows, '') .. ''
end
TEMPLATEDATA PART ------------------
-- A real parser/transformer would look differently but it would likely be much more complex -- The TemplateData-portion for Template:TemplateBox function p.templatedata(frame)
local tdata local args = frame.args or {} local formatting = args.formatting local pargs = ( frame:getParent() or {} ).args or {} local useTemplateData = pargs.useTemplateData
if (formatting == 'pretty' and useTemplateData ~= 'export') or (not useTemplateData) or (useTemplateData == 'export' and formatting ~= 'pretty') then local warning = "Warning: Module:TemplateBox - templatedata invoked but not requested by user (setting useTemplateData=1)." mw.log(warning) tdata = '{"description":"' .. warning .. '","params":{},"sets":[]}' return tdata end -- Load the JSON-Module which will convert LUA tables into valid JSON local JSON = require('Module:JSON') JSON.strictTypes = true -- Obtain the object containing info tdata = p.args2table(pargs, nil, 'templatedata') -- And finally return the result if formatting == 'pretty' then return JSON:encode_pretty(tdata) else return JSON:encode(tdata) end
end
return p