Module:Template test case

Revision as of 10:26, 24 November 2014 by wikipedia>Mr. Stradivarius (move arg-trimming code to the TableInvocation class)

Documentation for this module may be created at Module:Template test case/doc

-- This module provides several methods to generate test cases.

local mTableTools = require('Module:TableTools')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType

local TEMPLATE_NAME_MAGIC_WORD = '<TEMPLATE_NAME>'
local TEMPLATE_NAME_MAGIC_WORD_ESCAPED = TEMPLATE_NAME_MAGIC_WORD:gsub('%p', '%%%0')

-------------------------------------------------------------------------------
-- Helper functions
-------------------------------------------------------------------------------

--[[
local function validateTemplateOptions()
	-- Add the template names for the first two templates if they weren't
	-- specified.
	do
		local title = mw.title.getCurrentTitle().basePageTitle
		local template
		if title.namespace == 10 then
			template = title.text
		elseif title.namespace == 0 then
			template = ':' .. title.prefixedText
		else
			template = title.prefixedText
		end
		local templatePage = title.prefixedText
		templateOptions[1] = templateOptions[1] or {}
		templateOptions[1].templatePage = templateOptions[1].template or templatePage
		templateOptions[1].template = templateOptions[1].template or template
		templateOptions[2] = templateOptions[2] or {}
		templateOptions[2].templatePage = templateOptions[2].template or templatePage .. '/sandbox'
		templateOptions[2].template = templateOptions[2].template or template .. '/sandbox'
	end

	-- Validate options for three or more templates.
	if #templateOptions >= 3 then
		for i = 3, #templateOptions do
			local template = templateOptions[i].template
			if not template then
				error('arguments for a third or subsequent ' ..
				'template were found, but no template name ' ..
				'was specified', 3)
			end
		end
	end
end
--]]

local function parseTemplateOptions(origOptions)
	-- Given a table of raw options, returns a table of general options
	-- and template options.
	local templateOptions = mTableTools.numData(origOptions, true)
	local options = templateOptions.other
	templateOptions.other = nil
	return options, templateOptions
end

-------------------------------------------------------------------------------
-- Invocation class
-------------------------------------------------------------------------------

local Invocation = {}
Invocation.__index = Invocation

function Invocation.new()
	local obj = setmetatable({}, TableInvocation)
	return obj
end

function Invocation:setOptions(t)
	self._options = t
end

function Invocation:getOptions()
	return self._options
end

-------------------------------------------------------------------------------
-- Nowiki invocation class
-------------------------------------------------------------------------------

local NowikiInvocation = setmetatable({}, Invocation)
NowikiInvocation.__index = NowikiInvocation

function NowikiInvocation.new(args)
	checkType('NowikiInvocation.new', 'args', args, 'table')
	local obj = Invocation.new()
	setmetatable(obj, NowikiInvocation)

	obj.invocation = mw.text.unstrip(args.invocation)
	args.invocation = nil
	local options = {}
	for k, v in pairs(args) do
		options[k] = v
	end
	obj.options = options

	return obj
end

function NowikiInvocation:getInvocation(template)
	template = template:gsub('%%', '%%%%') -- Escape a % with %%
	local invocation, count = self.invocation:gsub(
		TEMPLATE_NAME_MAGIC_WORD_ESCAPED,
		template
	)
	if count < 1 then
		error(string.format(
			"the template invocation must include '%s' in place " ..
			"of the template name",
			TEMPLATE_NAME_MAGIC_WORD
		))
	end
	return invocation
end

function NowikiInvocation:getOutput(template)
	local invocation = self:getInvocation(template)
	return mw.getCurrentFrame():preprocess(invocation)
end

-------------------------------------------------------------------------------
-- Table invocation class
-------------------------------------------------------------------------------

local TableInvocation = {}
TableInvocation.__index = TableInvocation

function TableInvocation.new(args)
	checkType('TableInvocation.new', 'args', args, 'table')
	local obj = Invocation.new()
	setmetatable(obj, TableInvocation)

	local rawOptions, invokeArgs = {}, {}
	for k, v in pairs(args) do
		local optionKey = type(k) == 'string' and k:match('^_(.*)$')
		if optionKey then
			if type(v) == 'string' then
				v = mw.text.trim(v)
			end
			if v ~= '' then
				rawOptions[optionKey] = v
			end
		else
			invokeArgs[k] = v
		end
	end
	obj.invokeArgs = invokeArgs
	local options, templateOptions = parseTemplateOptions(rawOptions)
	obj:setOptions(options)
	obj:setTemplateOptions(templateOptions)

	return obj
end

function TableInvocation:getInvocation(template)
	return require('Module:Template invocation').invocation(
		template,
		self.invokeArgs
	)
end

function TableInvocation:getOutput(template)
	return mw.getCurrentFrame():expandTemplate{
		title = template,
		args = self.invokeArgs
	}
end

-------------------------------------------------------------------------------
-- TestCase class
-------------------------------------------------------------------------------

local TestCase = {}
TestCase.__index = TestCase

function TestCase.new(invocationObj)
	checkType('TestCase.new', 'invocationObj', invocationObj, 'table')
	local obj = setmetatable({}, TestCase)
	return obj
end

--------------------------------------------------------------------------------
-- Test case display functions
--
-- Test case display functions produce the wikitext to display the template
-- output for one test case. For example, one function might produce templates
-- aligned horizontally, and another function might produce templates aligned
-- one below the other.
--
-- They are named functions that accept the following parameters:
-- * templates - an array of subtables containing data about each template to be
--     displayed. These subtables can contain the following values:
--     * result - the expanded wikitext from the template.
--     * invocation - the original unexpanded wikitext that the output was
--         generated from. This may be nil if the invocation is not available.
--     * name - the name of the template.
--     * link - a normal wikilink to the template page (displays as
--         "Template:Foo").
--     * braceLink - a wikilink to the template page formatted like the {{tl}}
--         template, i.e. it displays as "{{Foo}}".
--     * heading - a heading to display above the template output.
--------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------

-- Table-based exports

local function getTableArgs(frame, wrappers)
	return require('Module:Arguments').getArgs(frame, {
		wrappers = wrappers,
		trim = false,
		removeBlanks = false
	})
end

local p = {}

function p._table(args)
	local invocation = TableInvocation.new(args)
	return invocation
end

function p.table(frame)
	return p._table(getTableArgs(frame, 'Template:Test case from arguments'))
end

function p.columns(frame)
	local args = getTableArgs(frame, 'Template:Testcase table')
	args._format = 'columns'
	return p._table(args)
end

function p.rows(frame)
	local args = getTableArgs(frame, 'Template:Testcase rows')
	args._format = 'rows'
	return p._table(args)
end

-- Nowiki-based exports

function p._nowiki(args)
	local invocation = NowikiInvocation.new(args)
	return invocation
end

function p.nowiki(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:Test case from invocation'
	})
	return p._nowiki(args)
end

-- Exports for testing

function p._exportClasses()
	return {
		TestCase = TestCase,
		Invocation = Invocation,
		NowikiInvocation = NowikiInvocation,
		TableInvocation = TableInvocation
	}
end

return p