-- <nowiki>
--------------------------------------------------------------------------------
-- A feature-packed example generator for brace-based wikitext.
--
-- @module T
-- @alias p
-- @version 0.6.4
-- @release experimental
-- @requires [[Global Lua Modules/Arguments|Module:Arguments]]
-- @requires [[Global Lua Modules/User error|Module:User error]]
-- @requires [[Global Lua Modules/Yesno|Module:Yesno]]
-- @author [[User:DarthKitty]]
-- @author [[User:Speedit]]
-- @author [[User:ExE Boss]]
--
-- @todo Extract CSS to stylesheet; transition from data-attributes to classes.
-- @todo Consider adding i18n for error messages, flags, &c.
-- @todo Consider adding generator(s?) for magic words and parser functions.
--------------------------------------------------------------------------------
local libraryUtil = require("libraryUtil")
local checkType = libraryUtil.checkType
local getArgs = require("Dev:Arguments").getArgs
local userError = require("Dev:User error")
local yesno = require("Dev:Yesno")
local p = {}
--------------------------------------------------------------------------------
-- Parses a parameter to get its components: its name (optional), and either its
-- value or its description (but not both).
--
-- @param {string} param
-- A parameter.
-- @returns {table}
-- The components of a parameter.
--------------------------------------------------------------------------------
local function parseParam(param)
local tmp = param
local name, value, description
-- the parameter's name is anything to the left of the first equals sign;
-- the equals sign can be escaped, for wikis that don't have [[Template:=]]
if tmp:find("=") or tmp:find(mw.text.nowiki("=")) then
name, tmp = tmp
:gsub(mw.text.nowiki("="), "=", 1)
:match("^(.-)%s*=%s*(.-)$")
end
-- if the remaining text is wrapped in matching quotes, then it's a literal
-- value; otherwise, it's a description of the parameter
local first = tmp:sub(1, 1)
local last = tmp:sub(-1)
if (first == '"' and last == '"') or (first == "'" and last == "'") then
value = tmp:sub(2, -2)
elseif tmp == "" then
description = "..." -- the description cannot be an empty string
else
description = tmp
end
return {
name = name,
value = value,
description = description
}
end
--------------------------------------------------------------------------------
-- The heart of the module. Transforms a list of parameters into wikitext
-- syntax.
--
-- @param {string} mode
-- Which kind of brace-based wikitext we're dealing with.
-- @param {string} opener
-- Text to insert between the two left-braces and the first parameter.
-- @param[opt] {table|nil} params
-- A sequentual table of parameters.
-- @param[opt] {table|nil} options
-- A table with configuration flags.
-- @param[opt] {boolean|nil} options.multiline
-- @param[opt] {boolean|nil} options.subst
-- @returns {string}
-- A blob of wikitext describing any brace-based syntax.
--------------------------------------------------------------------------------
local function builder(mode, opener, params, options)
checkType("builder", 2, opener, "string")
checkType("builder", 3, params, "table", true)
checkType("builder", 4, options, "table", true)
params = params or {}
options = options or {}
local html = mw.html.create("code")
:attr("data-t-role", "wrapper")
:attr("data-t-mode", mode)
:css("all", "unset")
:css("font-family", "monospace")
local openerSpan =
html:tag("span")
:attr("data-t-role", "opener")
:wikitext(mw.text.nowiki("{"):rep(2))
if options.subst then
openerSpan:wikitext("subst:")
end
openerSpan:wikitext(opener)
if options.multiline then
html:attr("data-t-multiline", "data-t-multiline")
end
for i, param in ipairs(params) do
if type(param) ~= "string" then
error("invalid entry #" .. i .. " in parameter list", 3)
end
local components = parseParam(param)
local paramHtml = html:tag("span")
:attr("data-t-role", "parameter")
:attr("data-t-index", i)
:wikitext(mw.text.nowiki("|"))
if options.multiline then
paramHtml:css("display", "block")
end
if components.name then
paramHtml:tag("span")
:attr("data-t-role", "parameter-name")
:css("font-weight", "bold")
:wikitext(components.name)
paramHtml:wikitext(" = ")
end
if components.value then
paramHtml:tag("span")
:attr("data-t-role", "parameter-value")
:wikitext(components.value)
end
if components.description then
paramHtml:tag("span")
:attr("data-t-role", "parameter-description")
:css("opacity", "0.65")
:wikitext(mw.text.nowiki("<"))
:wikitext(components.description)
:wikitext(mw.text.nowiki(">"))
end
end
html:tag("span")
:attr("data-t-role", "closer")
:wikitext(mw.text.nowiki("}"):rep(2))
return tostring(html)
end
--------------------------------------------------------------------------------
-- Resolves a transclusion according to the MediaWiki
-- transclusion resolution algorithm.
--
-- @param {string} title
-- The name of the transclusion to resolve.
-- @return {string}
-- The resolved transclusion with the namespace prefix.
-- @see [[w:Template:Transclude]]
--------------------------------------------------------------------------------
local function resolveTransclusion(title)
checkType("resolveTransclusion", 1, title, "string")
local i = mw.ustring.find(title, "%:")
if i == 1 then
return mw.ustring.sub(title, 2)
elseif i ~= nil then
return title
end
return "Template:" .. title
end
--------------------------------------------------------------------------------
-- Generator for transclusion syntax, e.g. `{{foo}}`, `{{:foo}}`,
-- or `{{Template:Foo}}`.
--
-- @param {string} title
-- The name of the template to link to.
-- @param[opt] {table|nil} params
-- A sequentual table of parameters.
-- @param[opt] {table|nil} options
-- A table with configuration flags.
-- @param[opt] {boolean|nil} options.multiline
-- @param[opt] {boolean|nil} options.subst
-- @returns {string}
-- A blob of wikitext describing a template.
--------------------------------------------------------------------------------
function p.transclusion(title, params, options)
if type(title) ~= "string" or title == "" then
error("no title specified", 2)
end
return builder(
"transclusion",
"[[:" .. resolveTransclusion(title) .. "|" .. title .. "]]",
params,
options
)
end
--------------------------------------------------------------------------------
-- Generator for invocation syntax, e.g. `{{#invoke:foo|bar}}`.
--
-- @param {string} title
-- The name of the module to link to, without the namespace prefix.
-- @param {string} func
-- The name of the function to call.
-- @param[opt] {table|nil} params
-- A sequentual table of parameters.
-- @param[opt] {table|nil} options
-- A table with configuration flags.
-- @param[opt] {boolean|nil} options.multiline
-- @param[opt] {boolean|nil} options.subst
-- @returns {string}
-- A blob of wikitext describing a module.
--------------------------------------------------------------------------------
function p.invocation(title, func, params, options)
if type(title) ~= "string" or title == "" then
error("no module specified", 2)
end
if type(func) ~= "string" or func == "" then
error("no function specified", 2)
end
local link = "[[Module:" .. title .. "|" .. title .. "]]"
return builder(
"invocation",
"#invoke:" .. link .. mw.text.nowiki("|") .. func,
params,
options
)
end
--------------------------------------------------------------------------------
-- Entry point from the wikitext side. Determines which generator to use based
-- on the provided arguments.
--
-- @param {table|Frame} frame
-- A frame object whose arguments will determine the correct generator
-- to use.
-- @returns {string}
-- A blob of wikitext describing any brace-based syntax.
--------------------------------------------------------------------------------
function p.main(frame)
local args = getArgs(frame, {removeBlanks = false})
local mode, minimumArity
if yesno(args.invocation) or yesno(args.i) then
mode = "invocation"
minimumArity = 2
else
mode = "transclusion"
minimumArity = 1
end
local params = {}
local options = {
multiline = yesno(args.multiline) or yesno(args.m),
subst = yesno(args.subst) or yesno(args.s),
}
-- a dynamically-generated list of arguments to the generator
-- required arguments are inserted before `params` and `options`
local varargs = {params, options}
for i, value in ipairs(args) do
if i <= minimumArity then
-- pass the first few values directly to the generator
-- these are used to calculate the opener
table.insert(varargs, i, value)
else
-- put the remaining values in a table, and pass it to the generator
-- these are shown as parameters in the resulting wikitext
params[#params + 1] = value
end
end
local success, response = pcall(p[mode], unpack(varargs))
return success and response or userError(response)
end
return p