Módulo:Exemplo teste predefinição

Documentação do módulo[ver] [editar] [histórico] [purgar]

Este módulo fornece uma estrutura para criar predefinições que produzem um exemplo para teste de predefinição. Embora os exemplos para testes possam ser criados manualmente, o uso de predefinições baseadas em Lua, como as fornecidas por este módulo, tem a vantagem de que os argumentos da predefinição só precisam ser inseridos uma vez, reduzindo assim o esforço envolvido na criação de exemplos para teste e reduzindo a possibilidade de erros na entrada.

Uso editar

Este módulo geralmente não deve ser chamado diretamente. Em vez disso, você deve usar um dos seguintes modelos:

Predefinições baseadas em parâmetros:

A única diferença entre essas predefiniçõed são os argumentos padrão. Por exemplo, é possível exibir exemplos para teste lado a lado em Predefinição:Exemplo teste linhas especificando |_format=columns

Predefinições baseadas em Nowiki:

Também é possível usar um formato de {{#invoke:Exemplo teste predefinição|main|parâmetros}}. Isso usa os mesmos padrões que Predefinição:Exemplo teste; consulte essa página para documentação dos parâmetros.

Não há interface direta para este módulo para outros módulos Lua. Os módulos Lua geralmente devem usar módulos de exemplo para teste baseados em Lua, como Módulo:UnitTests ou Módulo:ScribuntoUnit. Se for realmente necessário usar este módulo, você pode usar frame:expandTemplate com uma das predefinições listadas acima.

Configuração editar

Este módulo possui um módulo de configuração em Módulo:Exemplo teste predefinição/config. Você pode editá-lo para adicionar novas predefinições de wrapper ou para alterar as mensagens que o módulo gera.

Categorias de rastreamento/manutenção editar

--[[
   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