Module:Template test case

Nothing to hide, but nothing to show you either.
Revision as of 14:39, 24 November 2014 by wikipedia>Mr. Stradivarius (implement a default test case format)
Jump to navigation Jump to search

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')

-------------------------------------------------------------------------------
-- Template class
-------------------------------------------------------------------------------

local Template = {}
Template.__index = Template

function Template.new(invocationObj, options, titleCallback)
	local obj = setmetatable({}, Template)

	-- Set input
	for k, v in pairs(options or {}) do
		obj[k] = v
	end
	obj.invocation = invocationObj

	-- Validate template
	if not obj.template then
		if titleCallback then
			obj.title = titleCallback()
		else
			error('no template or title callback specified', 2)
		end
	end

	return obj
end

function Template:getFullPage()
	if self.template then
		local strippedTemplate, hasColon = self.template:gsub('^:', '', 1)
		local ns = strippedTemplate:match('^(.-):')
		ns = ns and mw.site.namespaces[ns]
		if ns then
			return strippedTemplate
		elseif hasColon then
			return strippedTemplate -- Main namespace
		else
			return mw.site.namespaces[10].name .. ':' .. strippedTemplate
		end
	else
		return self.title.prefixedText
	end
end

function Template:getName()
	if self.template then
		return self.template
	else
		return require('Module:Template invocation').name(self.title)
	end
end

function Template:makeLink(display)
	if display then
		return string.format('[[:%s|%s]]', self:getFullPage(), display)
	else
		return string.format('[[:%s]]', self:getFullPage())
	end
end

function Template:makeBraceLink(display)
	display = display or self:getName()
	local link = self:makeLink(display)
	return mw.text.nowiki('{{') .. link .. mw.text.nowiki('}}')
end

function Template:getInvocation(format)
	local invocation = self.invocation:getInvocation(self:getName())
	invocation = mw.text.nowiki(invocation)
	if format == 'code' then
		invocation = '<code>' .. invocation .. '</code>'
	elseif format == 'pre' then
		invocation = '<pre style="white-space: pre-wrap;">' .. invocation .. '</pre>'
		invocation = mw.getCurrentFrame():preprocess(invocation)
	end
	return invocation
end

function Template:getOutput()
	return self.invocation:getOutput(self:getName())
end

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

local TestCase = {}
TestCase.__index = TestCase

function TestCase.new(invocationObj, options)
	local obj = setmetatable({}, TestCase)

	-- Validate options
	do
		local highestNum = 0
		for k in pairs(options) do
			if type(k) == 'string' then
				local num = k:match('([1-9][0-9]*)$')
				num = tonumber(num)
				if num > highestNum then
					highestNum = num
				end
			end
		end
		for i = 3, highestNum do
			if not options['template' .. i] then
				error(string.format(
					"one or more options ending in '%d' were " ..
					"detected, but no 'template%d' option was found",
					i, i
				), 2)
			end
		end
	end

	-- Separate general options from options for specific templates
	local templateOptions = mTableTools.numData(options, true)
	obj.options = templateOptions.other

	-- Make the template objects
	obj.templates = {}
	local function templateTitleCallback()
		return mw.title.getCurrentTitle().basePageTitle
	end
	obj.templates[1] = Template.new(
		invocationObj,
		templateOptions[1],
		templateTitleCallback
	)
	obj.templates[2] = Template.new(
		invocationObj,
		templateOptions[2],
		function ()
			return templateTitleCallback():subPageTitle('sandbox')
		end
	)
	for i = 3, #templateOptions do
		table.insert(obj.templates, Template.new(
			invocationObj,
			templateOptions[i]
		))
	end

	return obj
end

function TestCase:renderDefault()
	local ret = {}
	ret[#ret + 1] = self.templates[1]:getInvocation('code')
	for i, obj in ipairs(self.templates) do
		ret[#ret + 1] = '<div style="clear: both;"></div>'
		ret[#ret + 1] = obj:makeBraceLink()
		ret[#ret + 1] = obj:getOutput()
	end
	return table.concat(ret, '\n\n')
end

function TestCase:tostring()
	local methods = {
		columns = 'renderColumns',
		rows = 'renderRows'
	}
	local format = self.options.format
	local method = format and methods[format] or 'renderDefault'
	return self[method](self)
end

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

local NowikiInvocation = {}
NowikiInvocation.__index = NowikiInvocation

function NowikiInvocation.new(invocation)
	local obj = setmetatable({}, NowikiInvocation)
	obj.invocation = mw.text.unstrip(invocation)
	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(invokeArgs)
	local obj = setmetatable({}, TableInvocation)
	obj.invokeArgs = invokeArgs
	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

-------------------------------------------------------------------------------
-- 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 options, 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 = v:match('^%s*(.-)%s*$') -- trim whitespace
			end
			if v ~= '' then
				options[optionKey] = v
			end
		else
			invokeArgs[k] = v
		end
	end
	local invocationObj = TableInvocation.new(invokeArgs)
	local testCaseObj = TestCase.new(invocationObj, options)
	return tostring(testCaseObj)
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 invocationObj = NowikiInvocation.new(args.invocation)
	args.invocation = nil
	local options = args
	local testCaseObj = TestCase.new(invocationObj, options)
	return tostring(testCaseObj)
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