Module:T/sandbox

From Commons Wiki
Jump to navigation Jump to search

Template:Sandbox module


-- <nowiki>
--------------------------------------------------------------------------------
-- A feature-packed example generator for brace-based wikitext.
--
-- @author [[User:DarthKitty]]
-- @author [[User:Speedit]]
-- @author [[User:Thundercraft5]]
-- @version 0.7.0
--
-- @todo Extract CSS to stylesheet; transition from data-attributes to classes.
-- @todo Modify `p.transclusion` to handle non-template namespaces.
-- @todo Consider adding i18n for error messages, flags, &c.
-- @todo Consider adding generator(s?) for magic words.
--------------------------------------------------------------------------------
local p = {}
local getArgs = require("Dev:Arguments").getArgs
local userError = require("Dev:User error")
local yesno = require("Dev:Yesno")

---------------------------------------------------------------------------------
-- Helper functions
---------------------------------------------------------------------------------
local function assertTrue(val, msg, level, ...)
	if not val then
		error(tostring(msg or 'assertion failed!'):format(...), (level or 1) < 0 and 0 or (level or 2)+1)
	end
	
	return val
end

local function assertFalse(val, msg, level, ...)
	if val then
		error(tostring(msg or 'assertion failed!'):format(...), (level or 1) < 0 and 0 or (level or 2)+1)
	end
	
	return val
end

local function toString(v)
	return v[1] and table.concat(v, v.sep) or v
end

local function makeLink(target, altText)
	return '[[' .. toString(target) .. (altText and '|' .. toString(altText) or '') .. ']]'
end

--------------------------------------------------------------------------------
-- 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 {{=}}
	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

--------------------------------------------------------------------------------
-- Formats the parameter using the parameter parsing function above. It inclues
-- the options given by the invocation as well.
-- 
-- @param {string} param
--	 A parameter
-- @param {table} options
--	 The options for the parameter
-- @param {string} sep
--	 The seperator for the parameter
-- @returns {string}
--	  The formatted and parsed parameter.
--------------------------------------------------------------------------------
local function formatParam(param, options, sep)
	local options = options or {}
	local paramHtml = mw.html.create('span')
		:attr("data-t-role", "parameter-components")
		:wikitext(mw.text.nowiki(assertTrue(sep, "No seperator specified", 3)))
		
	local components = parseParam(param)
		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 
		
	return tostring(paramHtml)
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 {table|nil} params
--	 A sequentual table of parameters.
-- @param {table|nil} options
--	 A table with configuration flags.
-- @returns {string}
--	 A blob of wikitext describing any brace-based syntax.
--------------------------------------------------------------------------------
local function builder(mode, opener, params, options)
	assertFalse(type(opener) ~= "string", "no opener specified", 3)

	if params == nil then
		params = {}
	else
		assertFalse(type(params) ~= "table", "invalid parameter list", 3)
	end

	if options == nil then
		options = {}
	else
		assertFalse(type(options) ~= "table", "invalid configuration options", 3)
	end

	local html = mw.html.create("code")
		:attr("data-t-role", "wrapper")
		:attr("data-t-mode", mode)
		:css("all", "unset")
		:css("font-family", "monospace")
	
	do
		local tmp = html:tag("span")
			:attr("data-t-role", "opener")
			:wikitext(mw.text.nowiki("{"):rep(2))
			
		if options.subst then
			tmp:attr("data-t-subst", "data-t-subst")
				:tag('span')
					:attr('data-t-role', 'subst')
					:wikitext('[[mw:Help:Substitution|subst]]')
					:tag('span')
						:attr('data-t-role', 'substitution-colon')
						:wikitext(mw.text.nowiki(':'))
					:done()
				:done()
		end	
		
		tmp:wikitext(opener)
		
		html = tmp:done()
	end
	
	if options.multiline then
		html:attr("data-t-multiline", "data-t-multiline")
	end

	for i, param in ipairs(params) do
		assertFalse(type(param) ~= 'string', "invalid entry #%q in parameter list", 3, i)
	
		local paramHtml = html:tag("span")
			:attr("data-t-role", "parameter")
			:attr("data-t-index", i)
			:wikitext(formatParam(param, options, "|"))
	end

	html:tag("span")
		:attr("data-t-role", "closer")
		:wikitext(mw.text.nowiki("}"):rep(2))

	return tostring(html)
end

--------------------------------------------------------------------------------
-- Generator for transclusion syntax, e.g. {{foo}}.
--
-- @param {string} title
--	 The name of the template to link to, without the namespace prefix.
-- @param {table|nil} params
--	 A sequentual table of parameters.
-- @param {table|nil} options
--	 A table with configuration flags.
-- @returns {string}
--	 A blob of wikitext describing a template.
--------------------------------------------------------------------------------
function p.transclusion(title, params, options)
	assertFalse(type(title) ~= "string" or title == "", "no title specified")

	local namespace = title:match('^(%a*):') or 'Template'
	title = title:gsub('^(%a*):', '')
	assertTrue(mw.site.namespaces[namespace], 'Invalid Namespace %q', 2, namespace)

	return builder(
		"transclusion",
		makeLink(namespace .. ":" .. title, mw.site.namespaces[namespace].id == 10 and title or namespace..':'..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 {table|nil} params
--	 A sequentual table of parameters.
-- @param {table|nil} options
--	 A table with configuration flags.
-- @returns {string}
--	 A blob of wikitext describing a module.
--------------------------------------------------------------------------------
function p.invocation(title, func, params, options)
	assertFalse(type(title) ~= "string" or title == "", "no module specified", 2)
	assertFalse(type(func) ~= "string" or title == "", "no function specified", 2)

	local link = makeLink("Module:" .. title, title)

	return builder(
		"invocation",
		"#invoke:" .. link .. mw.text.nowiki("|") .. func,
		params,
		options
	)
end

--------------------------------------------------------------------------------
-- Generator for parser function syntax, e.g. {{#if:foo|bar|baz}}.
--
-- @param {string} title
--	 The name of the module to link to, without the namespace prefix.
-- @param {table|nil} params
--	 A sequentual table of parameters.
-- @param {table|nil} options
--	 A table with configuration flags.
-- @returns {string}
--	 A blob of wikitext describing a parser function.
--------------------------------------------------------------------------------
function p.parser(title, params, options)
	assertFalse(type(title) ~= "string" or title == "", "no parser function specified", 2)
	params = params or {}

	local link = makeLink('mw:Help:Extension:ParserFunctions##'..title, '#'..title)
	local firstParam = table.remove(params, 1)
	
	return builder(
		"parser",
		link .. (firstParam and formatParam(firstParam, {}, ":") or mw.text.nowiki(':')),
		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
		
	elseif yesno(args.parser) or yesno(args.p) then
		mode = "parser"
		minimumArity = 1
	
	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.sub) 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

-- For debugging
-- p.builder = builder

return p