Módulo:Exemplo teste predefinição

--[[
   Um módulo para gerar predefinições de exemplos para testes.

    Este módulo incorpora código do módulo "Testcase table" da Wikipedia em 
    inglês,[1] escrito por Frietjes [2] com contribuições do Sr. Stradivarius [3]
    e Jackmcbarn,[4] e o módulo "Testcase rows" da Wikipédia em inglês,[5]
    escrito pelo Sr. Stradivarius.

    Os módulos "Testcase table" e "Testcase rows" são lançados sob a
    Licença CC BY-SA 3.0 [6] e GFDL.[7]

    Licença: CC BY-SA 3.0 e GFDL
    Autor: Sr. 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
]]

-- Carrega os módulos necessários
local yesno = require('Módulo:Yesno')

-- Define constantes
local DATA_MODULE = 'Módulo:Exemplo teste predefinição/data'

-------------------------------------------------------------------------------
-- Métodos compartilhados
-------------------------------------------------------------------------------

local function message(self, key, ...)
	-- Este método é adicionado às classes que precisam lidar com mensagens do
	-- módulo de configuração.
	local msg = self.cfg.msg[key]
	if select(1, ...) then
		return mw.message.newRawMessage(msg, ...):plain()
	else
		return msg
	end
end

-------------------------------------------------------------------------------
-- Classe de predefinição
-------------------------------------------------------------------------------

local Template = {}

Template.memoizedMethods = {
	-- Nomes de métodos a serem memorizados em cada objeto. Esta tabela deve 
	-- conter apenas métodos sem parâmetros.
	getFullPage = true,
	getName = true,
	makeHeader = true,
	getOutput = true
}

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

	-- Define entrada
	for k, v in pairs(options or {}) do
		if not Template[k] then
			obj[k] = v
		end
	end
	obj._invocation = invocationObj

	-- Valida entrada
	if not obj.template and not obj.title then
		error('nenhuma predefinição ou título especificados', 2)
	end

	-- Memoriza chamadas de métodos caras
	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 'Módulo' .. 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 -- Espaço nomeado principal
		else
			return mw.site.namespaces[10].name .. ':' .. strippedTemplate
		end
	end
end

function Template:getName()
	if self.template then
		return self.template
	else
		return require('Módulo: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: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 = '<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
		-- Marcações (tags) pre são o padrão
		invocation = mw.text.encode(invocation, '&')
		invocation = '<pre style="white-space: pre-wrap;">' .. invocation .. '</pre>'
		invocation = mw.getCurrentFrame():preprocess(invocation)
	end
	return invocation
end

function Template:getOutput()
	local protect = require('Módulo:Protect')
	-- calling self._invocation:getOutput{...}
	return protect(self._invocation.getOutput)(self._invocation, {
		template = self:getName(),
		requireMagicWord = self.requireMagicWord,
	})
end

-------------------------------------------------------------------------------
-- Classe TestCase
-------------------------------------------------------------------------------

local TestCase = {}
TestCase.__index = TestCase
TestCase.message = message -- adiciona o método de mensagem

TestCase.renderMethods = {
	-- As chaves nesta tabela são valores da opção "format", os valores são os
	-- método para renderizar esse formato.
	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

	-- Separa as opções gerais das opções de predefinição. As opções de 
	-- predefinição são numeradas, enquanto as opções gerais não são.
	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

	-- Define opções gerais
	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

	-- Pré-processa argumentos de predefinição
	for num, t in pairs(templateOptions) do
		if t.showtemplate ~= nil then
			t.showtemplate = yesno(t.showtemplate)
		end
	end

	-- Configura as duas primeiras tabelas de opções de predefinição, de modo 
	-- que se apenas a  "template3" for especificada, ela não será a primeira 
	-- predefinição quando o arranjo de opções da tabela for compactado.
	templateOptions[1] = templateOptions[1] or {}
	templateOptions[2] = templateOptions[2] or {}

	-- Permite que a opção "template" substitua a opção "template1" para
	-- compatibilidade retroativa com [[Módulo:Exemplo teste tabela]].
	if generalOptions.template then
		templateOptions[1].template = generalOptions.template
	end

	-- Adiciona opções de predefinição padrão
	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 opções de predefinição para quaisquer predefinições em que o 
	-- argumento showtemplate seja falso. Isso impede qualquer saída para essa
	-- predefinição.
	for num, t in pairs(templateOptions) do
		if t.showtemplate == false then
			templateOptions[num] = nil
		end
	end

	-- Verifica se há nomes de predefinição ausentes.
	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

	-- Compacta a tabela templateOptions para que possamos iterá-la com 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)

	-- Não requer a palavra mágica __TEMPLATENAME__ para invocações nowiki se
	-- há apenas uma predefinição sendo gerada.
	if #templateOptions <= 1 then
		templateOptions[1].requireMagicWord = false
	end

	mw.logObject(templateOptions)

	-- Faz os objetos de predefinição
	obj.templates = {}
	for i, options in ipairs(templateOptions) do
		table.insert(obj.templates, Template.new(invocationObj, options))
	end

	-- Adiciona categorias de rastreamento/manutenção. No momento estamos apenas
	-- acompanhando predefinições que usam qualquer parâmetro de "cabeçalho" ou
	-- um parâmetro de "saída".
	obj.categories = {}
	for k, v in pairs(options) do
		if type(k) == 'string' and k:find('heading') then
			obj.categories['!Exemplos para testes que usam parâmetros de cabeçalho'] = true
		elseif k == 'output' then
			obj.categories['!Exemplos para testes que usam parâmetro de output'] = 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()
	-- Retorna um booleano que mostra se todas as saídas de predefinição são
	-- iguais. As partes aleatórias dos marcadores de tira (veja 
	-- [[:en:Help:Strip markers]]) são removidas antes da comparação. Isso 
	-- significa que um marcador de tira pode conter qualquer coisa e ainda ser
	-- tratado como igual, mas resolve o problema de textos wiki idênticos não
	-- retornarem exatamente iguais.
	local function normaliseOutput(obj)
		local out = obj:getOutput()
		-- Remove as partes aleatórias dos marcadores de tira.
		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('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('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
		-- Legenda
		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

		-- Cabeçalhos
		local headerRow = tableroot:tag('tr')
		if self.options.rowheader then
			-- rowheader está correto aqui. Precisamos adicionar outra célula se
			-- rowheader for definido mais abaixo, mesmo se o header0 estiver
			-- faltando.
			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

	-- Cabeçalho de linha
	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
	
	-- Saída de predefinição
	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)
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')
		
		-- Cabeçalho
		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
		
		-- Saída de predefinição
		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)
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] = '<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')
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)

	-- Cabeçalho de linha
	if self.options.rowheader then
		dataRow:tag('th')
			:attr('scope', 'row')
			:newline()
			:wikitext(self.options.rowheader or self:message('row-header'))
	end
	-- Legenda
	if self.options.showcaption then
		dataRow:tag('th')
			:attr('scope', 'row')
			:newline()
			:wikitext(self.options.caption or self:message('columns-header'))
	end

	-- Mostrar código
	if self.options.showcode then
		dataRow:tag('td')
			:newline()
			:wikitext(self:getInvocation('code'))
	end

	-- Saída de predefinição
	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)
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] = '<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')
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('[[Categoria:%s]]', cat)
	end
	return ret
end

-------------------------------------------------------------------------------
-- Classe de invocação Nowiki
-------------------------------------------------------------------------------

local NowikiInvocation = {}
NowikiInvocation.__index = NowikiInvocation
NowikiInvocation.message = message -- Adiciona o método de mensagem

function NowikiInvocation.new(invocation, cfg)
	local obj = setmetatable({}, NowikiInvocation)
	obj.cfg = cfg
	invocation = mw.text.unstrip(invocation)
	-- Decodifica entidades de HTML para <, > e ". Isso significa que as 
	-- entidades de HTML no código original devem ser escapadas como, por 
	-- exemplo, &amp;lt;, o que é lamentável, mas é o melhor que podemos fazer,
	-- pois a distinção entre <, >, " e &lt;, &gt;, &quot; é perdida durante a
	-- operação original de nowiki.
	invocation = invocation:gsub('&lt;', '<')
	invocation = invocation:gsub('&gt;', '>')
	invocation = invocation:gsub('&quot;', '"')
	obj.invocation = invocation
	return obj
end

function NowikiInvocation:getInvocation(options)
	local template = options.template:gsub('%%', '%%%%') -- Escapa "%" com "%%"
	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

-------------------------------------------------------------------------------
-- Classe de invocação de tabela
-------------------------------------------------------------------------------

local TableInvocation = {}
TableInvocation.__index = TableInvocation
TableInvocation.message = message -- Adiciona o método de mensagem

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('Módulo: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

-------------------------------------------------------------------------------
-- Funções de ponte
--
-- Estas funções traduzem argumentos de predefinição em formulários que podem 
-- ser aceitos pelas diferentes classes e retornam os resultados.
-------------------------------------------------------------------------------

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*$') -- corta espaços em branco
			end
			if v ~= '' then
				options[optionKey] = v
			end
		else
			invokeArgs[k] = v
		end
	end

	-- Permite passar uma invocação nowiki como opção. Embora isso signifique 
	-- que os usuários tenham que passar o código duas vezes, os espaços em
	-- branco são preservados e &lt; etc. funcionarão conforme planejado.
	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)
	
	-- Converte argumentos começando com _ para consistência com a ponte normal
	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
	-- Supõe que nós queremos ver o código como já o passamos.
	newArgs.showcode = newArgs.showcode or true
	local testCaseObj = TestCase.new(invocationObj, newArgs, cfg)
	return tostring(testCaseObj)
end

-------------------------------------------------------------------------------
-- Exportações
-------------------------------------------------------------------------------

local p = {}

function p.main(frame, cfg)
	cfg = cfg or mw.loadData(DATA_MODULE)

	-- Carrega a configuração de wrapper, se houver.
	local wrapperConfig
	if frame.getParent then
		local title = frame:getParent():getTitle()
		local template = title:gsub(cfg.sandboxSubpagePattern, '')
		wrapperConfig = cfg.wrappers[template]
	end

	-- Elabora a função que nós iremos chamar, usa-a para gerar a configuração
	-- para Módulo:Arguments, e usa Módulo:Arguments para
	-- encontrar os argumentos passados pelo usuário.
	local func = wrapperConfig and wrapperConfig.func or 'table'
	local userArgs = require('Módulo:Arguments').getArgs(frame, {
		parentOnly = wrapperConfig,
		frameOnly = not wrapperConfig,
		trim = func ~= 'table',
		removeBlanks = func ~= 'table'
	})

	-- Obtém argumentos padrão e constrói a tabela de argumentos. Os argumentos 
	-- especificados pelo usuário substituem os argumentos padrão.
	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() -- Para teste
	return {
		Template = Template,
		TestCase = TestCase,
		NowikiInvocation = NowikiInvocation,
		TableInvocation = TableInvocation
	}
end

return p