Jump to content
Toggle menu
  • 8 articles
  • 75 files
  • 7 users
  • 37.8K edits
Neodyland Wiki
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:TemplateBox: Difference between revisions

From Neodyland Wiki
m Do not insert empty strings.
Prefix language interwiki links with w:
 
(52 intermediate revisions by 11 users not shown)
Line 1: Line 1:
require('strict')
--[[
    @exports
        usagesample( frame )
        argcount( frame )
        args2table( args, onGetKey, forCustom )
        paramtable( frame )
        description( frame )
        templatedata( frame )
]]
local p = {}
local p = {}


-- =p.test({ args={a="b"} })
-- Helper function, not exposed
function p.test(frame)
local function tobool(st)
    local args = frame.args
    local JSON = require('Module:JSON')
   
    return JSON:encode(args)
end
 
function tobool(st)
     if type( st ) == 'string' then
     if type( st ) == 'string' then
         return st == 'true'
         return st == 'true'
Line 17: Line 22:
end
end


-- InterfaceText:
 
-- A free-form string (no wikitext) in the content-language of the wiki, or, an object containing those strings keyed by language code.
-- 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"
 
local function initLangModule(frame)
    if langIsInit then
        return
    end
 
    userLang = frame:preprocess( '{{int:lang}}' )
 
    --! From [[:w:de:Modul:Expr]]; by [[:w:de:User:PerfektesChaos]];
    --! Derivative work: Rillke
    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 = {
local paraminfoTemplate = {
     description = {
     description = {
        required = true,
         default = '',
         default = '',
         t = 'InterfaceText',
         t = 'InterfaceText',
         alias = 'desc'
         alias = 'desc'
     }
     },
    format = {
default = 'inline',
t = 'selection',
selection = 'inline|block',
alias = 'print',
extract = function(pargs, number, paramVal)
local m = { multi = 'block', one = 'inline', infobox = 'block' }
            return m[paramVal] or 'inline'
        end
}
}
}
local paraminfoTLParams = {
local paraminfoTLParams = {
     label = {
     label = {
        required = false,
         default = '',
         default = '',
         t = 'InterfaceText'
         t = 'InterfaceText'
     },
     },
     required = {
     required = {
        required = true,
         default = false,
         default = false,
         extract = function(pargs, number, paramVal)
         extract = function(pargs, number, paramVal)
             local req = (pargs[number .. 'stat'] == 'required')
             local req = (pargs[number .. 'stat'] == 'required')
             return tobool( paramVal or req )
             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
         end
     },
     },
     description = {
     description = {
        required = false,
         default = '',
         default = '',
         t = 'InterfaceText',
         t = 'InterfaceText',
Line 48: Line 130:
     },
     },
     deprecated = {
     deprecated = {
        required = true,
         default = false,
         default = false,
         extract = function(pargs, number, paramVal)
         extract = function(pargs, number, paramVal)
Line 56: Line 137:
     },
     },
     aliases = {
     aliases = {
        required = false,
         default = '',
         default = '',
        t = 'table',
         extract = function(pargs, number, paramVal)
         extract = function(pargs, number, paramVal)
             local key = number .. 'aliases'
             local key = number .. 'aliases'
             local tdkey = key .. '-td'
             local tdkey = key .. '-td'
             local aliases = pargs[tdkey] or pargs[key]
             local aliases = pargs[tdkey] or pargs[key]
             if aliases then
             if aliases and mw.text.trim( aliases ) ~= '' then
                 aliases = mw.text.split( aliases, '/', true )
                 local cleaned = {}
                for m in mw.text.gsplit( aliases, '/', true ) do
                    cleaned[#cleaned+1] = mw.text.trim(m)
                end
                return cleaned
            else
                return nil
             end
             end
            return aliases
         end
         end
     },
     },
     default = {
     default = {
        required = false,
         default = '',
         default = '',
         t = 'InterfaceText',
         t = 'string',
         alias = 'def'
         alias = 'def'
     },
     },
     type = {
     type = {
        required = false,
         default = 'unknown',
         default = 'unknown',
         t = 'selection',
         t = 'selection',
         selection = 'unknown|number|string|string/wiki-user-name|string/wiki-page-name'
         selection = 'unknown|number|string|string/wiki-user-name|string/wiki-page-name|string/line|line|wiki-page-name|wiki-file-name|wiki-user-name|wiki-template-name|content|unbalanced-wikitext|date|url|boolean'
     },
     },
     inherits = {
     inherits = {
        required = false,
         default = nil,
         default = nil,
         t = 'string'
         t = 'string'
    },
    autovalue = {
        default = '',
        t = 'string',
        alias = 'av',
    },
    suggestedvalues = {
        default = '',
        t = 'table',
        alias = 'sv',
        extract = function(pargs, number, paramVal)
            if paramVal == nil then
                return nil
            end
            local cleaned = {}
            for m in mw.text.gsplit( paramVal, '/', true ) do
                cleaned[#cleaned+1] = mw.text.trim(m)
            end
            return cleaned
        end,
    },
    -- 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 = '<span style="font-family: monospace; color:#777; border:1px solid #6A6A6A">'
            param = '<code>' .. param .. '</code>'
            if item.aliases then
                alias = aliasTT .. table.concat(item.aliases, '</span><br />' .. aliasTT) .. '</span>'
                param = table.concat({param, '<br /><div>', alias, '</div>'})
            end
            renderCell(param)
        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 = '<span class="mw-templatedata-doc-muted" style="color:#777; font-variant:small-caps">' .. msg('param-default-empty') .. '</span>'
            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 = '<b>' .. msg('param-status-required') .. '</b>'
            elseif item.deprecated then
                stat = msg('param-status-deprecated')
            elseif item.suggested then
                stat = msg('param-status-suggested')
            end
            renderCell(stat)
        end
     }
     }
    -- sets will be treated differently because we can only have a plain structure in wikitext
}
}


-- Initialize param info
-- Initialize param info
function init()
-- Avoids having to add redundant information to the preceding tables
     for a, v in pairs( paraminfoTLParams ) do
local function init( which )
         if v.t ~= nil then
     local setDefault = function(v)
         if v.t == nil and v.default ~= nil then
             v.t = type( v.default )
             v.t = type( v.default )
         end
         end
         if v.selection then
         if v.selection then
            v.selection = '|' .. v.selection .. '|'
        local selection = mw.text.split(v.selection, '|', true)
        v.selection = {}
        for _, sel in ipairs(selection) do
        v.selection[sel] = true
        end
        end
    end
    for a, v in pairs( which ) do
        setDefault(v)
    end
end
local 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 sparseIpairs = require('Module:TableTools').sparseIpairs
    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 sparseIpairs( 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 = ('&nbsp;'):rep(maxArgLen - #arg)
         end
         end
        table.insert( args, argName .. space .. equal )
    end)
   
    if #args == 0 then
        sep = ''
        sepEnd = ''
        sepStart = ''
     end
     end
    if #templateTitle == 0 then
        templateTitle = mw.title.getCurrentTitle().text
    end
    result = table.concat( args, sep )
    result = table.concat({ mw.text.nowiki('{{'), 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
end
init()


function p.templatedata(frame)
------------------------------------------------------
     -- A real parser would look differently but it would likely be much more complex
------------------- GENERAL PART ---------------------
     local JSON = require('Module:JSON')
------------------------------------------------------
     local pargs = mw.clone( ( frame:getParent() or {} ).args or {} )
function p.args2table(args, onGetKey, consumer)
    initParamTables()
      
     local sets, asParamArray, laxtype, processParams, processDesc, unstrip
    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 = {}
     local templateArgs = {}
    -- Arguments that are localized (i.e. the user passed  1desc-en=English description of parameter one)
     local i18nTemplateArgs = {}
     local i18nTemplateArgs = {}
     local tdata = {
    -- Ensure that tables end up as array/object (esp. when they are empty)
        description = '',
     local tdata = {description="", params={}, sets={}}
        params = {
    local isObject = { __tostring = function() return "JSON object" end }    isObject.__index = isObject
         },
    local isArray  = { __tostring = function() return "JSON array"  end }    isArray.__index  = isArray
         sets = {
    setmetatable(tdata.params, isObject)
         }
    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 extractData = function( pi, number )
         local prefix = number or ''
         local prefix = number or ''
         local key, key2, tdkey, tdkey2, paramVal
         local ppv, paramVal
         local paramKey, paramTable
        local key1, key2, key3, key4
         local paramKey, paramTable, processKey
         if number then
         if number then
             paramKey = mw.text.trim( pargs[number] )
             paramKey = mw.text.trim( pargs[number] )
Line 123: Line 416:
                 paramKey = tostring( number )
                 paramKey = tostring( number )
             end
             end
             tdata.params[paramKey] = {}
              
             paramTable = tdata.params[paramKey]
            paramTable = {}
             if asParamArray then
                paramTable.key = paramKey
                table.insert(tdata.params, paramTable)
            else
                tdata.params[paramKey] = paramTable
            end
         end
         end
         for p, info in pairs( pi ) do
         for p, info in pairs( pi ) do
             key = prefix .. (info.alias or p)
             key1, key2, key3, key4 = onGetKey(prefix, info.alias, p)
             key2 = prefix .. p
             paramVal = nil
             tdkey = key .. '-td'
           
            tdkey2 = key2 .. '-td'
             processKey = function(key)
            paramVal = pargs[tdkey] or pargs[tdkey2] or pargs[key] or pargs[key2] or info.default
                if paramVal ~= nil then return end
            -- Do we have a multilingual item?
                local plain, multilingual = pargs[key], i18nTemplateArgs[key]
            if i18nTemplateArgs[tdkey2] then
                 paramVal = multilingual or plain
                 paramVal = i18nTemplateArgs[tdkey2]
             end
             end
             if i18nTemplateArgs[tdkey] then
             processKey( key1 )
                 paramVal = i18nTemplateArgs[tdkey]
            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
             end
             if 'function' == type( info.extract ) then
             if 'function' == type( info.extract ) then
                 if 'string' == type( paramVal ) then
                 if 'string' == type( paramVal ) then
Line 158: Line 468:
              
              
             if info.selection then
             if info.selection then
                 if info.selection:find( paramVal, 1, true ) then
                 if info.selection[paramVal] then
                     insertValue()
                     insertValue()
                 end
                 end
Line 166: Line 476:
                 end
                 end
             else
             else
                 if 'string' == info.t then
                local paramType = type( paramVal )
                 if 'string' == info.t and 'string' == paramType then
                     paramVal = mw.text.trim( paramVal )
                     paramVal = mw.text.trim( paramVal )
                     if '' ~= paramVal then
                     if '' ~= paramVal then
Line 177: Line 488:
                     paramVal = tonumber(paramVal)
                     paramVal = tonumber(paramVal)
                     insertValue()
                     insertValue()
                 elseif type( paramVal ) == info.t then
                 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()
                     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
             end
         end
         end
         -- Now, treat sets
         -- Now, treat sets
         key = prefix .. 'set'
         if sets then
        tdkey = prefix .. 'set-td'
            key1 = prefix .. 'set-td'
        paramVal = pargs[tdkey] or pargs[key]
            key2 = prefix .. 'set'
        if paramVal then
            paramVal = pargs[key1] or pargs[key2]
            local found = false
            if paramVal then
            for i, s in ipairs( tdata.sets ) do
                local found = false
                if s.label == paramVal then
                for i, s in ipairs( tdata.sets ) do
                    table.insert( s.params, p )
                    if s.label == paramVal then
                     found = true
                        table.insert( s.params, p )
                        found = true
                     end
                end
                if not found then
                    table.insert( tdata.sets, {
                        label = paramVal,
                        params = { p }
                    } )
                 end
                 end
            end
            if not found then
                table.insert( tdata.sets, {
                    label = paramVal,
                    params = { p }
                } )
             end
             end
         end
         end
    end
   
    if not pargs.templatedata then
        local warning = "Warning: Module:TemplateBox - templatedata invoked but not requested by user."
        mw.log(warning)
        tdata.description = warning
        return JSON:encode(tdata);
     end
     end
      
      
     -- First, analyse the structure of the provided arguments
     -- First, analyse the structure of the provided arguments
     for a, v in pairs( pargs ) do
     for a, v in pairs( pargs ) do
        if unstrip then
            v = mw.text.unstrip( v )
            pargs[a] = v
        end
         if type( a ) == 'number' then
         if type( a ) == 'number' then
             table.insert( templateArgs, a )
             table.insert( templateArgs, a )
Line 219: Line 541:
             local argAfter = {}
             local argAfter = {}
             local isTDArg = false
             local isTDArg = false
            local containsTD = a:find( '-td', 1, true )
             for i, part in ipairs( argSplit ) do
             for i, part in ipairs( argSplit ) do
                 if isTDArg then
                 if isTDArg or (containsTD == nil and i > 1) then
                     -- This is likely a language version
                     -- This is likely a language version
                     table.insert( argAfter, part )
                     table.insert( argAfter, part )
Line 230: Line 553:
                 end
                 end
             end
             end
             argUnitl = table.concat( argUnitl, '-' )
             if #argAfter > 0 then
            argAfter = table.concat( argAfter, '-' )
                argUnitl = table.concat( argUnitl, '-' )
            i18nTemplateArgs[argUnitl] = i18nTemplateArgs[argUnitl] or {}
                argAfter = table.concat( argAfter, '-' )
            i18nTemplateArgs[argUnitl][argAfter] = v
                i18nTemplateArgs[argUnitl] = i18nTemplateArgs[argUnitl] or {}
                i18nTemplateArgs[argUnitl][argAfter] = v
            end
         end
         end
     end
     end
     -- Then, start building the actual template
     -- Then, start building the actual template
     extractData( paraminfoTemplate )
     if processDesc then
     for i, number in pairs( templateArgs ) do
        extractData( paraminfoTemplate )
        extractData( paraminfoTLParams, number )
     end
    if processParams then
        -- Ensure that `templateArgs` contains indicies in ascending order
        table.sort( templateArgs )
        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)
    if type(input) == 'table' then
        input = require( 'Module:LangSwitch' )._langSwitch( input, userLang ) or ''
    end
    return input
end
 
function p.description(frame)
    local pargs = ( frame:getParent() or {} ).args or {}
 
    -- Initialize the language-related stuff
    initLangModule(frame)
 
    local tdata, paramLen
    tdata, paramLen = p.args2table(pargs, customOnGetKey, 'description')
    return toUserLanguage(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(frame)
 
    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, '<td>'
        if colspan > 1 then
            oTd = '<td colspan="' .. colspan .. '">'
        end
        row = table.concat({ row, oTd, wikitext, '</td>' })
    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) .. '</th>'
    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, toUserLanguage)
        end
        table.insert(rows, row)
    end
    return '<table class="wikitable templatebox-table"><tr>' .. table.concat(rows, '</tr><tr>') .. '</tr></table>'
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
     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
     -- And finally return the result
     return JSON:encode( tdata );
     if formatting == 'pretty' then
        return JSON:encode_pretty(tdata)
    else
        return JSON:encode(tdata)
    end
end
end


return p
return p

Latest revision as of 09:22, 12 August 2026

Module documentation[ create · purge ]
This documentation is transcluded from Module:TemplateBox/doc.
require('strict')

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

local p = {}

-- Helper function, not exposed
local 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"

local function initLangModule(frame)
    if langIsInit then
        return
    end

    userLang = frame:preprocess( '{{int:lang}}' )

    --! From [[:w:de:Modul:Expr]]; by [[:w:de:User:PerfektesChaos]]; 
    --! Derivative work: Rillke
    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'
    },
    format = {
		default = 'inline',
		t = 'selection',
		selection = 'inline|block',
		alias = 'print',
		extract = function(pargs, number, paramVal)
			local m = { multi = 'block', one = 'inline', infobox = 'block' }
            return m[paramVal] or 'inline'
        end
	}
}
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 and mw.text.trim( aliases ) ~= '' then
                local cleaned = {}
                for m in mw.text.gsplit( aliases, '/', true ) do
                    cleaned[#cleaned+1] = mw.text.trim(m)
                end
                return cleaned
            else
                return nil
            end
        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|wiki-template-name|content|unbalanced-wikitext|date|url|boolean'
    },
    inherits = {
        default = nil,
        t = 'string'
    },
    autovalue = {
        default = '',
        t = 'string',
        alias = 'av',
    },
    suggestedvalues = {
        default = '',
        t = 'table',
        alias = 'sv',
        extract = function(pargs, number, paramVal)
            if paramVal == nil then
                return nil
            end
            local cleaned = {}
            for m in mw.text.gsplit( paramVal, '/', true ) do
                cleaned[#cleaned+1] = mw.text.trim(m)
            end
            return cleaned
        end,
    },
    -- 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 = '<span style="font-family: monospace; color:#777; border:1px solid #6A6A6A">'

            param = '<code>' .. param .. '</code>'
            if item.aliases then
                alias = aliasTT .. table.concat(item.aliases, '</span><br />' .. aliasTT) .. '</span>'
                param = table.concat({param, '<br /><div>', alias, '</div>'})
            end
            renderCell(param)
        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 = '<span class="mw-templatedata-doc-muted" style="color:#777; font-variant:small-caps">' .. msg('param-default-empty') .. '</span>'
            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 = '<b>' .. msg('param-status-required') .. '</b>'
            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
local 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
        	local selection = mw.text.split(v.selection, '|', true)
        	v.selection = {}
        	for _, sel in ipairs(selection) do
        		v.selection[sel] = true
        	end
        end
    end
    for a, v in pairs( which ) do
        setDefault(v)
    end
end
local 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 sparseIpairs = require('Module:TableTools').sparseIpairs
    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 sparseIpairs( 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 = ('&nbsp;'):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('{{'), 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, unstrip
    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 isObject = { __tostring = function() return "JSON object" end }    isObject.__index = isObject
    local isArray  = { __tostring = function() return "JSON array"  end }    isArray.__index  = isArray
    setmetatable(tdata.params, isObject)
    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[paramVal] 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
        -- Ensure that `templateArgs` contains indicies in ascending order
        table.sort( templateArgs )
        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)
    if type(input) == 'table' then
        input = require( 'Module:LangSwitch' )._langSwitch( input, userLang ) or ''
    end
    return input
end

function p.description(frame)
    local pargs = ( frame:getParent() or {} ).args or {}

    -- Initialize the language-related stuff
    initLangModule(frame)

    local tdata, paramLen
    tdata, paramLen = p.args2table(pargs, customOnGetKey, 'description')
    return toUserLanguage(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(frame)

    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, '<td>'
        if colspan > 1 then
            oTd = '<td colspan="' .. colspan .. '">'
        end
        row = table.concat({ row, oTd, wikitext, '</td>' })
    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) .. '</th>'
    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, toUserLanguage)
        end
        table.insert(rows, row)
    end
    return '<table class="wikitable templatebox-table"><tr>' .. table.concat(rows, '</tr><tr>') .. '</tr></table>'
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
Cookies help us deliver our services. By using our services, you agree to our use of cookies.