Bước tới nội dung

Mô đun:Template test case

Bách khoa toàn thư mở Wikipedia
--[[   A module for generating test case templates.   This module incorporates code from the English Wikipedia's "Testcase table"   module,[1] written by Frietjes [2] with contributions by Mr. Stradivarius [3]   and Jackmcbarn,[4] and the English Wikipedia's "Testcase rows" module,[5]   written by Mr. Stradivarius.   The "Testcase table" and "Testcase rows" modules are released under the   CC BY-SA 3.0 License [6] and the GFDL.[7]   License: CC BY-SA 3.0 and the GFDL   Author: Mr. Stradivarius   [1] https://en.wikipedia.org/wiki/Module:Testcase_table   [2] https://en.wikipedia.org/wiki/User:Frietjes   [3] https://en.wikipedia.org/wiki/User:Mr._Stradivarius   [4] https://en.wikipedia.org/wiki/User:Jackmcbarn   [5] https://en.wikipedia.org/wiki/Module:Testcase_rows   [6] https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License   [7] https://en.wikipedia.org/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License]]-- Load required moduleslocal yesno = require('Module:Yesno')-- Set constantslocal DATA_MODULE = 'Module:Template test case/data'--------------------------------------------------------------------------------- Shared methods-------------------------------------------------------------------------------local function message(self, key, ...)	-- This method is added to classes that need to deal with messages from the	-- config module.	local msg = self.cfg.msg[key]	if select(1, ...) then		return mw.message.newRawMessage(msg, ...):plain()	else		return msg	endend--------------------------------------------------------------------------------- Template class-------------------------------------------------------------------------------local Template = {}Template.memoizedMethods = {	-- Names of methods to be memoized in each object. This table should only	-- hold methods with no parameters.	getFullPage = true,	getName = true,	makeHeader = true,	getOutput = true}function Template.new(invocationObj, options)	local obj = {}	-- Set input	for k, v in pairs(options or {}) do		if not Template[k] then			obj[k] = v		end	end	obj._invocation = invocationObj	-- Validate input	if not obj.template and not obj.title then		error('không có bản mẫu hoặc tiêu đề được định rõ', 2)	end	-- Memoize expensive method calls	local memoFuncs = {}	return setmetatable(obj, {		__index = function (t, key)			if Template.memoizedMethods[key] then				local func = memoFuncs[key]				if not func then					local val = Template[key](t)					func = function () return val end					memoFuncs[key] = func				end				return func			else				return Template[key]			end		end	})endfunction Template:getFullPage()	if not self.template then		return self.title.prefixedText	elseif self.template:sub(1, 7) == '#invoke' then		return 'Module' .. self.template:sub(8):gsub('|.*', '')	else		local strippedTemplate, hasColon = self.template:gsub('^:', '', 1)		hasColon = hasColon > 0		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	endendfunction Template:getName()	if self.template then		return self.template	else		return require('Module:Template invocation').name(self.title)	endendfunction Template:makeLink(display)	if display then		return string.format('[[:%s|%s]]', self:getFullPage(), display)	else		return string.format('[[:%s]]', self:getFullPage())	endendfunction Template:makeBraceLink(display)	display = display or self:getName()	local link = self:makeLink(display)	return mw.text.nowiki('{{') .. link .. mw.text.nowiki('}}')endfunction Template:makeHeader()	return self.heading or self:makeBraceLink()endfunction Template:getInvocation(format)	local invocation = self._invocation:getInvocation{		template = self:getName(),		requireMagicWord = self.requireMagicWord,	}	if format == 'code' then		invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>'	elseif format == 'kbd' then		invocation = '<kbd>' .. mw.text.nowiki(invocation) .. '</kbd>'	elseif format == 'plain' then		invocation = mw.text.nowiki(invocation)	else		-- Default is pre tags		invocation = mw.text.encode(invocation, '&')		invocation = '<pre style="white-space: pre-wrap;">' .. invocation .. '</pre>'		invocation = mw.getCurrentFrame():preprocess(invocation)	end	return invocationendfunction Template:getOutput()	local protect = require('Module:Protect')	-- calling self._invocation:getOutput{...}	return protect(self._invocation.getOutput)(self._invocation, {		template = self:getName(),		requireMagicWord = self.requireMagicWord,	})end--------------------------------------------------------------------------------- TestCase class-------------------------------------------------------------------------------local TestCase = {}TestCase.__index = TestCaseTestCase.message = message -- add the message methodTestCase.renderMethods = {	-- Keys in this table are values of the "format" option, values are the	-- method for rendering that format.	columns = 'renderColumns',	rows = 'renderRows',	tablerows = 'renderRows',	inline = 'renderInline',	cells = 'renderCells',	default = 'renderDefault'}function TestCase.new(invocationObj, options, cfg)	local obj = setmetatable({}, TestCase)	obj.cfg = cfg	-- Separate general options from template options. Template options are	-- numbered, whereas general options are not.	local generalOptions, templateOptions = {}, {}	for k, v in pairs(options) do		local prefix, num		if type(k) == 'string' then			prefix, num = k:match('^(.-)([1-9][0-9]*)$')		end		if prefix then			num = tonumber(num)			templateOptions[num] = templateOptions[num] or {}			templateOptions[num][prefix] = v		else			generalOptions[k] = v		end	end	-- Set general options	generalOptions.showcode = yesno(generalOptions.showcode)	generalOptions.showheader = yesno(generalOptions.showheader) ~= false	generalOptions.showcaption = yesno(generalOptions.showcaption) ~= false	generalOptions.collapsible = yesno(generalOptions.collapsible)	generalOptions.notcollapsed = yesno(generalOptions.notcollapsed)	generalOptions.wantdiff = yesno(generalOptions.wantdiff) 	obj.options = generalOptions	-- Preprocess template args	for num, t in pairs(templateOptions) do		if t.showtemplate ~= nil then			t.showtemplate = yesno(t.showtemplate)		end	end	-- Set up first two template options tables, so that if only the	-- "template3" is specified it isn't made the first template when the	-- the table options array is compressed.	templateOptions[1] = templateOptions[1] or {}	templateOptions[2] = templateOptions[2] or {}	-- Allow the "template" option to override the "template1" option for	-- backwards compatibility with [[Module:Testcase table]].	if generalOptions.template then		templateOptions[1].template = generalOptions.template	end	-- Add default template options	if templateOptions[1].template and not templateOptions[2].template then		templateOptions[2].template = templateOptions[1].template ..			'/' .. obj.cfg.sandboxSubpage	end	if not templateOptions[1].template then		templateOptions[1].title = mw.title.getCurrentTitle().basePageTitle	end	if not templateOptions[2].template then		templateOptions[2].title = templateOptions[1].title:subPageTitle(			obj.cfg.sandboxSubpage		)	end	-- Remove template options for any templates where the showtemplate	-- argument is false. This prevents any output for that template.	for num, t in pairs(templateOptions) do		if t.showtemplate == false then			templateOptions[num] = nil		end	end	-- Check for missing template names.	for num, t in pairs(templateOptions) do		if not t.template and not t.title then			error(obj:message(				'missing-template-option-error',				num, num			), 2)		end	end	-- Compress templateOptions table so we can iterate over it with ipairs.	templateOptions = (function (t)		local nums = {}		for num in pairs(t) do			nums[#nums + 1] = num		end		table.sort(nums)		local ret = {}		for i, num in ipairs(nums) do			ret[i] = t[num]		end		return ret	end)(templateOptions)	-- Don't require the __TEMPLATENAME__ magic word for nowiki invocations if	-- there is only one template being output.	if #templateOptions <= 1 then		templateOptions[1].requireMagicWord = false	end	mw.logObject(templateOptions)	-- Make the template objects	obj.templates = {}	for i, options in ipairs(templateOptions) do		table.insert(obj.templates, Template.new(invocationObj, options))	end	-- Add tracking categories. At the moment we are only tracking templates	-- that use any "heading" parameters or an "output" parameter.	obj.categories = {}	for k, v in pairs(options) do		if type(k) == 'string' and k:find('heading') then			obj.categories['Các trường hợp kiểm thử sử dụng các tham số phần mở đầu'] = true		elseif k == 'output' then			obj.categories['Các trường hợp kiểm thử sử dụng tham số đầu ra'] = true		end	end	return objendfunction TestCase:getTemplateOutput(templateObj)	local output = templateObj:getOutput()	if self.options.resetRefs then		mw.getCurrentFrame():extensionTag('references')	end	return outputendfunction TestCase:templateOutputIsEqual()	-- Returns a boolean showing whether all of the template outputs are equal.	-- The random parts of strip markers (see [[Help:Strip markers]]) are	-- removed before comparison. This means a strip marker can contain anything	-- and still be treated as equal, but it solves the problem of otherwise	-- identical wikitext not returning as exactly equal.	local function normaliseOutput(obj)		local out = obj:getOutput()		-- Remove the random parts from strip markers.		out = out:gsub('(\127[^\127]*UNIQ%-%-%l+%-)%x+(%-%-?QINU[^\127]*\127)', '%1%2')		return out	end	local firstOutput = normaliseOutput(self.templates[1])	for i = 2, #self.templates do		local output = normaliseOutput(self.templates[i])		if output ~= firstOutput then			return false		end	end	return trueendfunction TestCase:makeCollapsible(s)	local title = self.options.title or self.templates[1]:makeHeader()	if self.options.titlecode then		title = self.templates[1]:getInvocation('kbd')	end	local isEqual = self:templateOutputIsEqual()	local root = mw.html.create('div')	root		:addClass('mw-collapsible')		:css('width', '100%')		:css('border', 'solid silver 1px')		:css('padding', '0.2em')		:css('clear', 'both')		:addClass(self.options.notcollapsed == false and 'mw-collapsed' or nil)	if self.options.wantdiff then		root			:tag('div')				:css('background-color', isEqual and 'yellow' or '#90a8ee')				:css('color', 'black')				:css('font-weight', 'bold')				:css('padding', '0.2em')				:wikitext(title)				:done()	else		if self.options.notcollapsed ~= true or false then			root				:addClass(isEqual and 'mw-collapsed' or nil)		end		root			:tag('div')				:css('background-color', isEqual and 'lightgreen' or 'yellow')				:css('color', 'black')				:css('font-weight', 'bold')				:css('padding', '0.2em')				:wikitext(title)				:done()	end	root		:tag('div')			:addClass('mw-collapsible-content')			:newline()			:wikitext(s)			:newline()	return tostring(root)endfunction TestCase:renderColumns()	local root = mw.html.create()	if self.options.showcode then		root			:wikitext(self.templates[1]:getInvocation())			:newline()	end	local tableroot = root:tag('table')	if self.options.showheader then		-- Caption		if self.options.showcaption then			tableroot				:addClass(self.options.class)				:cssText(self.options.style)				:tag('caption')					:wikitext(self.options.caption or self:message('columns-header'))		end		-- Headers		local headerRow = tableroot:tag('tr')		if self.options.rowheader then			-- rowheader is correct here. We need to add another th cell if			-- rowheader is set further down, even if heading0 is missing.			headerRow:tag('th'):wikitext(self.options.heading0)		end		local width		if #self.templates > 0 then			width = tostring(math.floor(100 / #self.templates)) .. '%'		else			width = '100%'		end		for i, obj in ipairs(self.templates) do			headerRow				:tag('th')					:css('width', width)					:wikitext(obj:makeHeader())		end	end	-- Row header	local dataRow = tableroot:tag('tr'):css('vertical-align', 'top')	if self.options.rowheader then		dataRow:tag('th')			:attr('scope', 'row')			:wikitext(self.options.rowheader)	end		-- Template output	for i, obj in ipairs(self.templates) do		if self.options.output == 'nowiki+' then			dataRow:tag('td')				:newline()				:wikitext(self.options.before)				:wikitext(self:getTemplateOutput(obj))				:wikitext(self.options.after)				:wikitext('<pre style="white-space: pre-wrap;">')				:wikitext(mw.text.nowiki(self.options.before or ""))				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))				:wikitext(mw.text.nowiki(self.options.after or ""))				:wikitext('</pre>')		elseif self.options.output == 'nowiki' then			dataRow:tag('td')				:newline()				:wikitext(mw.text.nowiki(self.options.before or ""))				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))				:wikitext(mw.text.nowiki(self.options.after or ""))		else			dataRow:tag('td')				:newline()				:wikitext(self.options.before)				:wikitext(self:getTemplateOutput(obj))				:wikitext(self.options.after)		end	end		return tostring(root)endfunction TestCase:renderRows()	local root = mw.html.create()	if self.options.showcode then		root			:wikitext(self.templates[1]:getInvocation())			:newline()	end	local tableroot = root:tag('table')	tableroot		:addClass(self.options.class)		:cssText(self.options.style)	if self.options.caption then		tableroot			:tag('caption')				:wikitext(self.options.caption)	end	for _, obj in ipairs(self.templates) do		local dataRow = tableroot:tag('tr')				-- Header		if self.options.showheader then			if self.options.format == 'tablerows' then				dataRow:tag('th')					:attr('scope', 'row')					:css('vertical-align', 'top')					:css('text-align', 'left')					:wikitext(obj:makeHeader())				dataRow:tag('td')					:css('vertical-align', 'top')					:css('padding', '0 1em')					:wikitext('→')			else				dataRow:tag('td')					:css('text-align', 'center')					:css('font-weight', 'bold')					:wikitext(obj:makeHeader())				dataRow = tableroot:tag('tr')			end		end				-- Template output		if self.options.output == 'nowiki+' then			dataRow:tag('td')				:newline()                :wikitext(self.options.before)                :wikitext(self:getTemplateOutput(obj))                :wikitext(self.options.after)                :wikitext('<pre style="white-space: pre-wrap;">')                :wikitext(mw.text.nowiki(self.options.before or ""))                :wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))                :wikitext(mw.text.nowiki(self.options.after or ""))                :wikitext('</pre>')		elseif self.options.output == 'nowiki' then			dataRow:tag('td')				:newline()				:wikitext(mw.text.nowiki(self.options.before or ""))				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))				:wikitext(mw.text.nowiki(self.options.after or ""))		else			dataRow:tag('td')				:newline()				:wikitext(self.options.before)				:wikitext(self:getTemplateOutput(obj))				:wikitext(self.options.after)		end	end	return tostring(root)endfunction TestCase:renderInline()	local arrow = mw.language.getContentLanguage():getArrow('forwards')	local ret = {}	for i, obj in ipairs(self.templates) do		local line = {}		line[#line + 1] = self.options.prefix or '* '		if self.options.showcode then			line[#line + 1] = obj:getInvocation('code')			line[#line + 1] = ' '			line[#line + 1] = arrow			line[#line + 1] = ' '		end		if self.options.output == 'nowiki+' then			line[#line + 1] = self.options.before or ""			line[#line + 1] = self:getTemplateOutput(obj)			line[#line + 1] = self.options.after or ""			line[#line + 1] = '<pre style="white-space: pre-wrap;">'			line[#line + 1] = mw.text.nowiki(self.options.before or "")			line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))			line[#line + 1] = mw.text.nowiki(self.options.after or "")			line[#line + 1] = '</pre>'		elseif self.options.output == 'nowiki' then			line[#line + 1] = mw.text.nowiki(self.options.before or "")			line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))			line[#line + 1] = mw.text.nowiki(self.options.after or "")		else			line[#line + 1] = self.options.before or ""			line[#line + 1] = self:getTemplateOutput(obj)			line[#line + 1] = self.options.after or ""		end		ret[#ret + 1] = table.concat(line)	end	if self.options.addline then		local line = {}		line[#line + 1] = self.options.prefix or '* '		line[#line + 1] = self.options.addline		ret[#ret + 1] = table.concat(line)	end	return table.concat(ret, '\n')endfunction TestCase:renderCells()	local root = mw.html.create()	local dataRow = root:tag('tr')	dataRow		:css('vertical-align', 'top')		:addClass(self.options.class)		:cssText(self.options.style)	-- Row header	if self.options.rowheader then		dataRow:tag('th')			:attr('scope', 'row')			:newline()			:wikitext(self.options.rowheader or self:message('row-header'))	end	-- Caption	if self.options.showcaption then		dataRow:tag('th')			:attr('scope', 'row')			:newline()			:wikitext(self.options.caption or self:message('columns-header'))	end	-- Show code	if self.options.showcode then		dataRow:tag('td')			:newline()			:wikitext(self:getInvocation('code'))	end	-- Template output	for i, obj in ipairs(self.templates) do		if self.options.output == 'nowiki+' then			dataRow:tag('td')				:newline()				:wikitext(self.options.before)				:wikitext(self:getTemplateOutput(obj))				:wikitext(self.options.after)				:wikitext('<pre style="white-space: pre-wrap;">')				:wikitext(mw.text.nowiki(self.options.before or ""))				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))				:wikitext(mw.text.nowiki(self.options.after or ""))				:wikitext('</pre>')		elseif self.options.output == 'nowiki' then			dataRow:tag('td')				:newline()				:wikitext(mw.text.nowiki(self.options.before or ""))				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))				:wikitext(mw.text.nowiki(self.options.after or ""))		else			dataRow:tag('td')				:newline()				:wikitext(self.options.before)				:wikitext(self:getTemplateOutput(obj))				:wikitext(self.options.after)		end	end	return tostring(root)endfunction TestCase:renderDefault()	local ret = {}	if self.options.showcode then		ret[#ret + 1] = self.templates[1]:getInvocation()	end	for i, obj in ipairs(self.templates) do		ret[#ret + 1] = '<div style="clear: both;"></div>'		if self.options.showheader then			ret[#ret + 1] = obj:makeHeader()		end		if self.options.output == 'nowiki+' then			ret[#ret + 1] = (self.options.before or "") ..			self:getTemplateOutput(obj) ..			(self.options.after or "") ..			'<pre style="white-space: pre-wrap;">' ..			mw.text.nowiki(self.options.before or "") ..			mw.text.nowiki(self:getTemplateOutput(obj)) ..			mw.text.nowiki(self.options.after or "") .. '</pre>'		elseif self.options.output == 'nowiki' then			ret[#ret + 1] = mw.text.nowiki(self.options.before or "") ..			mw.text.nowiki(self:getTemplateOutput(obj)) ..			mw.text.nowiki(self.options.after or "")		else			ret[#ret + 1] = (self.options.before or "") ..			self:getTemplateOutput(obj) ..			(self.options.after or "")		end	end	return table.concat(ret, '\n\n')endfunction TestCase:__tostring()	local format = self.options.format	local method = format and TestCase.renderMethods[format] or 'renderDefault'	local ret = self[method](self)	if self.options.collapsible then		ret = self:makeCollapsible(ret)	end	for cat in pairs(self.categories) do		ret = ret .. string.format('[[Thể loại:%s]]', cat)	end	return retend--------------------------------------------------------------------------------- Nowiki invocation class-------------------------------------------------------------------------------local NowikiInvocation = {}NowikiInvocation.__index = NowikiInvocationNowikiInvocation.message = message -- Add the message methodfunction NowikiInvocation.new(invocation, cfg)	local obj = setmetatable({}, NowikiInvocation)	obj.cfg = cfg	invocation = mw.text.unstrip(invocation)	-- Decode HTML entities for <, >, and ". This means that HTML entities in	-- the original code must be escaped as e.g. &amp;lt;, which is unfortunate,	-- but it is the best we can do as the distinction between <, >, " and &lt;,	-- &gt;, &quot; is lost during the original nowiki operation.	invocation = invocation:gsub('&lt;', '<')	invocation = invocation:gsub('&gt;', '>')	invocation = invocation:gsub('&quot;', '"')	obj.invocation = invocation	return objendfunction NowikiInvocation:getInvocation(options)	local template = options.template:gsub('%%', '%%%%') -- Escape "%" with "%%"	local invocation, count = self.invocation:gsub(		self.cfg.templateNameMagicWordPattern,		template	)	if options.requireMagicWord ~= false and count < 1 then		error(self:message(			'nowiki-magic-word-error',			self.cfg.templateNameMagicWord		))	end	return invocationendfunction NowikiInvocation:getOutput(options)	local invocation = self:getInvocation(options)	return mw.getCurrentFrame():preprocess(invocation)end--------------------------------------------------------------------------------- Table invocation class-------------------------------------------------------------------------------local TableInvocation = {}TableInvocation.__index = TableInvocationTableInvocation.message = message -- Add the message methodfunction TableInvocation.new(invokeArgs, nowikiCode, cfg)	local obj = setmetatable({}, TableInvocation)	obj.cfg = cfg	obj.invokeArgs = invokeArgs	obj.code = nowikiCode	return objendfunction TableInvocation:getInvocation(options)	if self.code then		local nowikiObj = NowikiInvocation.new(self.code, self.cfg)		return nowikiObj:getInvocation(options)	else		return require('Module:Template invocation').invocation(			options.template,			self.invokeArgs		)	endendfunction TableInvocation:getOutput(options)	if (options.template:sub(1, 7) == '#invoke') then		local moduleCall = mw.text.split(options.template, '|', true)		local args = mw.clone(self.invokeArgs)		table.insert(args, 1, moduleCall[2])		return mw.getCurrentFrame():callParserFunction(moduleCall[1], args)	end	return mw.getCurrentFrame():expandTemplate{		title = options.template,		args = self.invokeArgs	}end--------------------------------------------------------------------------------- Bridge functions---- These functions translate template arguments into forms that can be accepted-- by the different classes, and return the results.-------------------------------------------------------------------------------local bridge = {}function bridge.table(args, cfg)	cfg = cfg or mw.loadData(DATA_MODULE)	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	-- Allow passing a nowiki invocation as an option. While this means users	-- have to pass in the code twice, whitespace is preserved and &lt; etc.	-- will work as intended.	local nowikiCode = options.code	options.code = nil	local invocationObj = TableInvocation.new(invokeArgs, nowikiCode, cfg)	local testCaseObj = TestCase.new(invocationObj, options, cfg)	return tostring(testCaseObj)endfunction bridge.nowiki(args, cfg)	cfg = cfg or mw.loadData(DATA_MODULE)		-- Convert args beginning with _ for consistency with the normal bridge	local newArgs = {}	for k, v in pairs(args) do		local normalName = type(k) == "string" and string.match(k, "^_(.*)$")		if normalName then			newArgs[normalName] = v		else			newArgs[k] = v		end	end	local code = newArgs.code or newArgs[1]	local invocationObj = NowikiInvocation.new(code, cfg)	newArgs.code = nil	newArgs[1] = nil	-- Assume we want to see the code as we already passed it in.	newArgs.showcode = newArgs.showcode or true	local testCaseObj = TestCase.new(invocationObj, newArgs, cfg)	return tostring(testCaseObj)end--------------------------------------------------------------------------------- Exports-------------------------------------------------------------------------------local p = {}function p.main(frame, cfg)	cfg = cfg or mw.loadData(DATA_MODULE)	-- Load the wrapper config, if any.	local wrapperConfig	if frame.getParent then		local title = frame:getParent():getTitle()		local template = title:gsub(cfg.sandboxSubpagePattern, '')		wrapperConfig = cfg.wrappers[template]	end	-- Work out the function we will call, use it to generate the config for	-- Module:Arguments, and use Module:Arguments to find the arguments passed	-- by the user.	local func = wrapperConfig and wrapperConfig.func or 'table'	local userArgs = require('Module:Arguments').getArgs(frame, {		parentOnly = wrapperConfig,		frameOnly = not wrapperConfig,		trim = func ~= 'table',		removeBlanks = func ~= 'table'	})	-- Get default args and build the args table. User-specified args overwrite	-- default args.	local defaultArgs = wrapperConfig and wrapperConfig.args or {}	local args = {}	for k, v in pairs(defaultArgs) do		args[k] = v	end	for k, v in pairs(userArgs) do		args[k] = v	end	return bridge[func](args, cfg)endfunction p._exportClasses() -- For testing	return {		Template = Template,		TestCase = TestCase,		NowikiInvocation = NowikiInvocation,		TableInvocation = TableInvocation	}endreturn p