Module:Infobox: Difference between revisions
From The Lands Of Liberos Project
Content added Content deleted
m (1 revision imported) |
No edit summary Tag: Reverted |
||
Line 15: | Line 15: | ||
-- instead of re-reading the page with mw.title.new in p.categoryDoc |
-- 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') |
|||
-------------------------------------------------------------------------------- |
-------------------------------------------------------------------------------- |
Revision as of 20:38, 29 November 2021
This Lua module is used on approximately 4,130,000 pages, or roughly 387793% of all pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
This module depends on the following other modules: |
This module uses TemplateStyles: |
Module:Infobox is a module that implements the {{Infobox}} template. Please see the template page for usage instructions.
Tracking categories
- Category:Pages which use infobox templates with ignored data cells (0)
- Category:Articles which use infobox templates with no data rows (0)
- Category:Pages which use embedded infobox templates with the title parameter (0)
-- <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
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 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