Module:Infobox
Jump to navigation
Jump to search
Template:Docbunto
Subpages
-- <nowiki>
--------------------------------------------------------------------------------
-- Infobox template module for [[w:c:dev]] documentation.
--
-- @see [[:Category:Infobox templates]]
-- @usage {{#invoke:Infobox}}
-- @module infobox
-- @alias p
-- @version 1.1.2
-- @author Speedit
-- @author DarthKitty
--
-- @todo Fill holes in the documentation by replacing question marks.
-- @todo Use the already loaded data for sorting category data by name
-- instead of re-reading the page with mw.title.new in p.categoryDoc
--------------------------------------------------------------------------------
local p = {}
local yesno = require('Dev:Yesno')
local getArgs = require('Dev:Arguments').getArgs
local userError = require('Dev:User error')
local wdsButton = require('Dev:WDS Button')
local i18n = require('Dev:I18n').loadMessages(
'Infobox',
'Common',
'Testharness'
)
local entrypoint = require('Dev:Entrypoint')
local data = mw.loadData('Dev:Infobox/data')
local title = mw.title.getCurrentTitle()
require('Dev:No interwiki access')
--------------------------------------------------------------------------------
-- Date formatter utility.
--
-- @see [[Template:FormatDate]]
--
-- @param {string} d
-- Unprocessed date.
-- @param {string} f
-- Date format to use.
-- @returns {string}
-- Formatted, localised date.
--------------------------------------------------------------------------------
local function dtfm(d, f)
return mw.getCurrentFrame():expandTemplate{
title = 'FormatDate',
args = {
[1] = d,
dateformat = f,
uselang = i18n:getLang()
}
}
end
--------------------------------------------------------------------------------
-- Breadcrumb link generator.
--
-- @param {string} t
-- Breadcrumb part.
-- @param {table} parts
-- Collection of title parts.
-- @returns {string}
-- Breadcrumb chunk.
--------------------------------------------------------------------------------
local function crumbpart(parts)
local d = #parts
return table.concat({
(d == 1 and '< ' or ' | '),
'[[',
table.concat(parts, '/'),
'|',
parts[d],
']]'
})
end
--------------------------------------------------------------------------------
-- Infobox breadcrumb generator for mobile.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Breadcrumb designed for mobile.
--------------------------------------------------------------------------------
function p.breadcrumbs(frame)
local parts = {}
local ret = mw.html.create('center')
for t in tostring(title.fullText):gmatch('[^/]+') do
table.insert(parts, t)
ret:wikitext(crumbpart(parts))
end
return frame:preprocess(tostring(ret))
end
--------------------------------------------------------------------------------
-- Infobox data argument handler. Substitutes '$n' arguments with version
-- numbers.
--
-- @usage {{#invoke:infobox|data|{{{Data}}}|ucfirst=1}}
--
-- @param {Frame} frame
-- Frame invocation object.
-- @param {string} frame.args[1]
-- Infobox data input.
-- @param {string} frame.args.ucfirst
-- Capitalization boolean.
-- @throws {string}
-- 'missing argument from Module:Infobox in p.data'
-- @returns {string}
-- Argument-substituted infobox data.
--------------------------------------------------------------------------------
function p.data(frame)
if not (frame.args or {})[1] then
error('missing argument from Module:Infobox in p.data')
end
local tArgs = frame:getParent().args
local ret = frame.args[1]
local uc1 = yesno(mw.text.trim(frame.args.ucfirst or ''))
if not string.find(ret, '%$') then
return ret
end
-- Argument substitution.
local function repl(d)
local rsub = d == '1'
and (tArgs.Submodule or i18n:msg('original'))
or (tArgs['Submodule' .. d] or i18n:msg('version', d))
return uc1
and rsub:gsub('^%l', mw.ustring.upper)
or rsub
end
ret = ret:gsub('%$(%d+)', repl)
return ret
end
--------------------------------------------------------------------------------
-- Infobox date list generator with version numbers.
--
-- @usage {{#invoke:infobox|date}}
--
-- @param {Frame} frame
-- Frame invocation object.
-- @param {string} frame.args
-- Invocation arguments.
-- @param {string} frame.args.ext
-- Code type.
-- @param {table} frame:getParent().args
-- Template arguments.
-- @returns {string}
-- Formatted multi-line date string.
--------------------------------------------------------------------------------
function p.date(frame)
local tArgs = frame:getParent().args
local dateFmt = tArgs.dateformat or ''
local ret = ''
if tArgs.Updated and #tArgs.Updated > 0 then
-- Generated first formatted date.
ret = dtfm(tArgs.Updated, dateFmt)
if tArgs.Submodule or tArgs.Updated2 then
-- Append first version tag.
local sub1 = tArgs.Submodule or i18n:msg('original')
if sub1 ~= '' then
ret = ret .. ' (' .. sub1 .. ')'
end
end
-- Handle further versions.
for d = 2, math.huge do
local p = tArgs['Updated' .. d]
if not p then
break
end
local s = tArgs['Submodule' .. d] or i18n:msg('version', d)
ret = (d == 2 and '* ' or '') .. ret
.. '\n* '
.. dtfm(p, dateFmt)
.. ' (' .. s .. ')'
end
-- Default date field.
elseif tArgs.Code then
-- @todo Use DPL template to extract main code page?
local ext = frame.args.ext or 'js'
local suffix = '.' .. ext
local u = frame:expandTemplate{
title = 'Updated',
args = {
'MediaWiki:' .. title.baseText .. suffix
}
}
ret = dtfm(u, dateFmt)
end
return ret
end
--------------------------------------------------------------------------------
-- Category formatter.
--
-- @param {table} tbl
-- Array of text items to be returned.
-- @param {string} cat
-- Category name.
-- @param {string} sortkey
-- Category sortkey.
--------------------------------------------------------------------------------
local function category(tbl, cat, sortkey)
table.insert(tbl, '[[Category:')
table.insert(tbl, cat)
if sortkey and sortkey ~= '' then
table.insert(tbl, '|')
table.insert(tbl, sortkey)
end
table.insert(tbl, ']]')
end
--------------------------------------------------------------------------------
-- Returns error for missing description param in infobox.
--
-- @returns {string}
-- Error message and trancking category
--------------------------------------------------------------------------------
function p.description()
return userError('Description missing', 'Content without description')
end
--------------------------------------------------------------------------------
-- Infobox category generator for type subcategorization.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Type categories corresponding to `Type` infobox argument.
--------------------------------------------------------------------------------
function p.categories(frame)
local ret = {}
local typ = frame.args[1]
local tArgs = p.getParent(frame).args
local catKeys = tArgs.Type
if
mw.ustring.lower(tArgs.Status or '') == 'archive' or
not data.categories[typ or ''] or
title.namespace ~= 0
then
return ''
end
local sortkey = tArgs.Title or title.prefixedText
category(ret, typ, sortkey)
table.insert(ret, '[[:Category:')
table.insert(ret, typ)
table.insert(ret, '|')
table.insert(ret, typ)
table.insert(ret, ']]')
-- Maintenance category
local REPORTCAT = 'Content without type categorization'
local TYPEDOC = ':Category:' .. REPORTCAT .. '#Documentation'
if catKeys then
for v in mw.text.gsplit(mw.ustring.lower(catKeys), '%s*,%s*') do
local cat = data.categories[typ][mw.text.trim(v)]
if cat then
category(ret, cat, sortkey)
end
end
elseif typ == 'JavaScript' then
table.insert(ret, '<br />')
table.insert(ret, userError('Type categorization missing', REPORTCAT))
table.insert(ret, ' ([[:' .. TYPEDOC .. '|')
table.insert(
ret,
mw.message.new('oasis-more'):useDatabase(false):plain()
)
table.insert(ret, ']])')
end
return table.concat(ret)
end
--------------------------------------------------------------------------------
-- Category documentation generator.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @param {table} frame.args
-- Frame argument table.
-- @param {string} frame.args[1]
-- Infobox type corresponding to [[Module:Infobox/data]].
-- @returns {string}
-- Table of types against descriptions.
--------------------------------------------------------------------------------
function p.categoryDoc(frame)
local typ = frame.args[1] or ''
local ret = mw.html.create('table'):attr {
['class'] = 'WikiaTable',
['border'] = '1',
['id'] = 'types'
}
ret:tag('tr')
:tag('th'):wikitext(i18n:msg('type'))
:done()
:tag('th'):wikitext(i18n:msg('description'))
:done()
if
not data.categories[typ] or
not data.descriptions[typ]
then
return ret
end
-- Extract categories from data.
local catData = {}
local catNames = {}
for k, n in pairs(data.categories[typ]) do
if not catData[n] then
catNames[#catNames + 1] = n
catData[n] = {k}
else
catData[n][#catData[n] + 1] = k
end
if
not catData[n].description and
data.descriptions[typ][k]
then
catData[n].description = data.descriptions[typ][k]
end
end
-- Sort category data by name.
local dataContent = mw.title.new('Module:Infobox/data'):getContent()
local function sortKey(a, b)
local i1, i2 =
dataContent:find('"' .. a .. '"'),
dataContent:find('"' .. b .. '"')
return i1 < i2
end
table.sort(catNames, sortKey)
-- Render documentation table.
local cat, catRow, catKeys, desc
for i, n in ipairs(catNames) do
cat = catData[n]
catRow = ret:tag('tr')
catKeys = catRow:tag('td')
-- Handle multiple keys.
if #cat >= 2 then
catKeys = catKeys:tag('ul')
for i, k in ipairs(cat) do
catKeys
:tag('li'):tag('code')
:wikitext(k)
end
else
catKeys
:tag('code')
:wikitext(cat[1])
end
-- Add description.
desc = cat.description:gsub('^%l', mw.ustring.upper) .. '.'
catRow:tag('td')
:wikitext('[[:Category:' .. n .. ']]')
:tag('p'):wikitext(desc)
end
return tostring(ret)
end
--------------------------------------------------------------------------------
-- Category description generator. Used on category pages to describe pages.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Category description followed by parent category link.
--------------------------------------------------------------------------------
function p.categoryDesc(frame)
local typ = frame.args[1] or ''
local key = frame.args[2] or ''
local ret = data.messages.description
local desc = (data.descriptions[typ] or {})[key]
if not desc then
error('misconfigured arguments in p.categoryDesc from Module:Install')
end
ret = ret:gsub('$1', desc) .. '[[Category:' .. typ .. '|{{SUBPAGENAME}}]]'
return frame:preprocess(ret)
end
--------------------------------------------------------------------------------
-- Alias mapper for {{t|Infobox Lua}} `Type` argument.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Lua type (`'invocable'` or `'meta'`).
--------------------------------------------------------------------------------
function p.luaType(frame)
local tArgs = frame:getParent().args
local typ = tArgs.Type or tArgs.type
return typ
and data.luaTypes[typ:lower()]
or ''
end
--------------------------------------------------------------------------------
-- Test suite status.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string}
-- Test suite status badge.
--------------------------------------------------------------------------------
function p.luaSuite(frame)
local page =
frame:getParent().Code or
mw.language.fetchLanguageName(title.subpageText) == ''
and title.subpageText
or title.baseText:match('/*([^/]+)$')
-- DPL query for categories
local query = table.concat({
'{{#dpl:',
'| debug = 0',
'| mode = userformat',
'| allowcachedresults = 1',
'| category = Lua test suites',
'| titleregexp = ^' .. mw.uri.encode(page, 'WIKI') .. '/testcases$',
'| addcategories = true',
'| format = ,%CATNAMES%,,',
'}}'
}, '\n')
-- Fetch category data.
local cats = mw.text.split(frame:preprocess(query), ',')
--[=[
-- @todo Fix this:
if cats[2] and data.luaStatus[cats[2]]
and cats[1] == 'Lua test suites'
and cats[2] ~= 'Lua test suites'
then
-- DPL sorts pages alphabetically, so if [[Module talk:PAGE/testcases]]
-- contains [[:Category:Lua test suites]], then [[:Category:Skipped Lua test suites]]
-- and [[:Category:Pages with script errors]] end up ingored,
-- and the test suite is incorrectly reported as passing.
cats[1], cats[2] = cats[2], cats[1]
end
]=]
local i18n_key = data.luaStatus[cats[1] or ''] or 'unknown'
-- Build badge.
local badge = mw.html.create('div')
:addClass('plainlinks')
:wikitext(
'[' ..
mw.site.server ..
mw.uri.fullUrl('Module talk:' .. page .. '/testcases').path ..
' ' ..
wdsButton._badge(i18n:msg(i18n_key), i18n_key) ..
']'
)
return tostring(badge)
end
--- Set of valid scope aliases
local scopeAliases = {
p = "personal",
s = "site-wide",
mw = "vanilla mediawiki",
}
--- Set of valid scope names
local validScopes = {
["personal"] = true,
["site-wide"] = true,
["vanilla mediawiki"] = true,
}
--- Dictionary of "scope" → "i18n" key mapping
local scopeLangMap = {
["site-wide"] = "sitewide",
["vanilla mediawiki"] = "vanilla-mw"
}
--- Dictionary of "scope" → "category" subpage mapping
local scopeCategoryMap = {
["personal"] = "Personal use",
["site-wide"] = "Site-wide use",
["vanilla mediawiki"] = "Vanilla MediaWiki use"
}
--- The order in which scopes are displayed in the infobox
local scopeOrder = {
"personal",
"site-wide",
"vanilla mediawiki",
}
--------------------------------------------------------------------------------
-- Function used by {{T|scope}}.
--
-- @param {Frame|table} frame
-- Scribunto Frame object or table of arguments.
-- @returns {string}
-- The parsed scopes
--------------------------------------------------------------------------------
function p.parseScope(frame)
local args = getArgs(frame)
local arg1 = mw.ustring.lower(args[1] or "")
local type = args.type
local rec = yesno(args.pr)
local usingLegacyAlias = false
local hasUnknownScope = false
local scopes = {}
local function explodeScope(scopeName, callback)
if not scopeName or #scopeName == 0 then return end
scopeName = scopeAliases[scopeName] or scopeName
if validScopes[scopeName] then
scopes[scopeName] = true
elseif scopeName == "ps" then
-- Legacy compat
scopes["personal"] = true
scopes["site-wide"] = true
usingLegacyAlias = true
elseif callback then
callback(scopeName)
else
hasUnknownScope = true
end
end
local function explodeScope2(splitSymbol)
for splitWS in mw.text.gsplit(splitSymbol, "%s+") do
explodeScope(splitWS)
end
end
for splitBr in mw.text.gsplit(arg1, "%s-<br%f[%s/>].-/?>%s*") do
for splitSymbol in mw.text.gsplit(splitBr, "%s*[/,]%s*") do
explodeScope(splitSymbol, explodeScope2)
end
end
local cats = {}
local result = {}
for _, scopeName in ipairs(scopeOrder) do
if scopes[scopeName] then
if type then
table.insert(cats, "[[Category:" .. type .. "/" .. scopeCategoryMap[scopeName] .. "]]")
end
local scopeString = i18n:msg(scopeLangMap[scopeName] or scopeName)
if scopeName == "personal" and rec then
scopeString = scopeString .. " (" .. i18n:msg("recommended") .. ")"
end
table.insert(result, scopeString)
end
end
if (#result == 0 or hasUnknownScope) and type then
table.insert(cats, "[[Category:" .. type .. "/Unknown use]]")
end
if usingLegacyAlias then
table.insert(cats, "[[Category:Pages using deprecated Template:Scope values]]")
end
if (#result > 1) then
local ul = mw.html.create("ul")
for _, v in ipairs(result) do
ul:tag("li"):wikitext(v)
end
return tostring(ul) .. table.concat(cats)
end
return (result[1] or i18n:msg("unknown")) .. table.concat(cats)
end
--------------------------------------------------------------------------------
-- Template wrapper for [[Template:Infobox]].
--
-- @usage {{#invoke:infobox|main}}
--
-- @function p.main
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {string|nil}
-- Package function output.
--------------------------------------------------------------------------------
p.main = entrypoint(p)
--------------------------------------------------------------------------------
-- Returns topmost parent frame.
--
-- @param {Frame} frame
-- Frame invocation object.
-- @returns {table}
-- Highest parent frame.
--------------------------------------------------------------------------------
function p.getParent(frame)
local cf = frame
local pf = frame:getParent()
if pf then
return p.getParent(pf)
else
return cf
end
end
return p