--[[    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 modules local yesno = require('Module:Yesno')  -- Set constants local 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 	end end  -- 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('no template or title specified', 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 	}) end  function 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 	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', 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('') end  function Template:makeHeader() 	return self.heading or self:makeBraceLink() end  function Template:getInvocation(format) 	local invocation = self._invocation:getInvocation{ 		template = self:getName(), 		requireMagicWord = self.requireMagicWord, 	} 	if format == 'code' then 		invocation = '' .. mw.text.nowiki(invocation) .. '' 	elseif format == 'kbd' then 		invocation = '' .. mw.text.nowiki(invocation) .. '' 	elseif format == 'plain' then 		invocation = mw.text.nowiki(invocation) 	else 		-- Default is pre tags 		invocation = mw.text.encode(invocation, '&') 		invocation = '' .. invocation .. '' 		invocation = mw.getCurrentFrame():preprocess(invocation) 	end 	return invocation end  function 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 = TestCase TestCase.message = message -- add the message method  TestCase.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['Test cases using heading parameters'] = true 		elseif k == 'output' then 			obj.categories['Test cases using output parameter'] = true 		end 	end  	return obj end  function TestCase:getTemplateOutput(templateObj) 	local output = templateObj:getOutput() 	if self.options.resetRefs then 		mw.getCurrentFrame():extensionTag('references') 	end 	return output end  function 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 true end  function 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) end  function 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('') 				:wikitext(mw.text.nowiki(self.options.before or "")) 				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj))) 				:wikitext(mw.text.nowiki(self.options.after or "")) 				:wikitext('') 		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) end  function 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('')                 :wikitext(mw.text.nowiki(self.options.before or ""))                 :wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))                 :wikitext(mw.text.nowiki(self.options.after or ""))                 :wikitext('') 		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) end  function 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] = '' 			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] = '' 		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') end  function 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('') 				:wikitext(mw.text.nowiki(self.options.before or "")) 				:wikitext(mw.text.nowiki(self:getTemplateOutput(obj))) 				:wikitext(mw.text.nowiki(self.options.after or "")) 				:wikitext('') 		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) end  function 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] = '' 		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 "") .. 			'' .. 			mw.text.nowiki(self.options.before or "") .. 			mw.text.nowiki(self:getTemplateOutput(obj)) .. 			mw.text.nowiki(self.options.after or "") .. '' 		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') end  function 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('Category:%s', cat) 	end 	return ret end  -- Nowiki invocation class  local NowikiInvocation = {} NowikiInvocation.__index = NowikiInvocation NowikiInvocation.message = message -- Add the message method  function 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. &lt;, which is unfortunate, 	-- but it is the best we can do as the distinction between <, >, " and <, 	-- >, " is lost during the original nowiki operation. 	invocation = invocation:gsub('<', '<') 	invocation = invocation:gsub('>', '>') 	invocation = invocation:gsub('"', '"') 	obj.invocation = invocation 	return obj end  function 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 invocation end  function NowikiInvocation:getOutput(options) 	local invocation = self:getInvocation(options) 	return mw.getCurrentFrame():preprocess(invocation) end  -- Table invocation class  local TableInvocation = {} TableInvocation.__index = TableInvocation TableInvocation.message = message -- Add the message method  function TableInvocation.new(invokeArgs, nowikiCode, cfg) 	local obj = setmetatable({}, TableInvocation) 	obj.cfg = cfg 	obj.invokeArgs = invokeArgs 	obj.code = nowikiCode 	return obj end  function 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 		) 	end end  function 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 < 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) end  function 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) end  function p._exportClasses() -- For testing 	return { 		Template = Template, 		TestCase = TestCase, 		NowikiInvocation = NowikiInvocation, 		TableInvocation = TableInvocation 	} end  return p