Module:TemplateBox

--   @exports        usagesample( frame )        argcount( frame )        args2table( args, onGetKey, forCustom )        paramtable( frame )        templatedata( frame )

local p = {}

-- 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 sepEnd = ( pargs.lines == 'multi' ) and ' ' or '' local sep = sepEnd local args, argName = {} sep = sep .. ' |'   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 pargs[i .. 'stat'] ~= 'optional-' then table.insert( args, argName .. ' = ' ) end end end if #args == 0 then sep = '' sepEnd = '' end return sep .. table.concat( args, sep ) .. sepEnd end

-- 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 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-optional'] = "optional" i18n['param-required'] = "required" i18n['param-deprecated'] = "deprecated" i18n['param-empty'] = "empty"

-- 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 = { required = true, default = '', t = 'InterfaceText', alias = 'desc' } } local paraminfoTLParams = { label = { required = false, default = '', t = 'InterfaceText' },   required = { required = true, default = false, extract = function(pargs, number, paramVal) local req = (pargs[number .. 'stat'] == 'required') return tobool( paramVal or req ) end },   description = { required = false, default = '', t = 'InterfaceText', alias = 'd'   }, deprecated = { required = true, default = false, extract = function(pargs, number, paramVal) local depr = (pargs[number .. 'stat'] == 'deprecated') return tobool( paramVal or depr ) end },   aliases = { required = false, 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 = { required = false, default = '', t = 'string', alias = 'def' },   type = { required = false, default = 'unknown', t = 'selection', selection = 'unknown|number|string|string/wiki-user-name|string/wiki-page-name|string/line' },   inherits = { required = false, default = nil, t = 'string' }   -- sets will be treated differently because we can only have a plain structure in wikitext } local tableLayout = { {       col = 'param-name', cols = 2, width = '20%', extract = function(item, renderCell, monolingual) local label, alias, param = item.label, "", item.key renderCell(monolingual(label)) param = ' ' if item.aliases then alias = '' .. table.concat(alias, ', ') .. '' param = param .. ' ' .. alias .. ' '           end renderCell(param) end }, {        col = 'param-desc', width = '60%', extract = function(item, renderCell, monolingual) renderCell(monolingual(item.description)) end }, {        col = 'param-default', width = '10%', extract = function(item, renderCell, monolingual) local def = monolingual(item.default) or '' if #def == 0 then def = ' ' .. msg('param-empty') .. ' '           end renderCell(def) end }, {        col = 'param-status', width = '10%', extract = function(item, renderCell, monolingual) local stat = msg('param-optional') if item.required then stat = ' .. msg('param-required') .. '           elseif item.deprecated then stat = msg('param-deprecated') 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 init( paraminfoTemplate ) init( paraminfoTLParams )

-- --- GENERAL PART - -- function p.args2table(args, onGetKey, forCustom) local sets, asParamArray, laxtype if forCustom then asParamArray = true laxtype = true else sets = 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 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)            ppv = pargs[key1] or pargs[key2] or pargs[key3] or pargs[key4] or info.default -- Do we have a multilingual item? paramVal = i18nTemplateArgs[key1] or i18nTemplateArgs[key2] or i18nTemplateArgs[key3] or i18nTemplateArgs[key4] 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 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 not asParamArray then extractData( paraminfoTemplate ) end for i, number in pairs( templateArgs ) do       extractData( paraminfoTLParams, number ) end return tdata, #templateArgs end

-- CUSTOM PARAMETER TABLE PART - -- function p.paramtable(frame) --! From de:Modul:Expr; by de:User:PerfektesChaos; --! Derivative work: Rillke local userLang = mw.message.newRawMessage( '' ):parse

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

local pargs = ( frame:getParent or {} ).args or {} local onGetKey, tdata, paramLen if 'only' == pargs.useTemplateData then return 'param table - output suppressed' end -- A custom key-pref-function onGetKey = 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 tdata, paramLen = p.args2table(pargs, onGetKey, true) if 0 == paramLen then return '' end local row, rows = '', {} local monolingual = function(wikitext) if type(wikitext) == 'table' then wikitext = frame:expandTemplate{ title = 'LangSwitch', args = wikitext } end return wikitext end local renderCell = function(wikitext) row = row .. ' ' .. 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 = ''

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 ' ' 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 pargs = ( frame:getParent or {} ).args or {} if not pargs.useTemplateData 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, false) -- And finally return the result return JSON:encode(tdata) end

return p