Zum Inhalt springen

„Modul:Test“ – Versionsunterschied

aus Wikipedia, der freien Enzyklopädie
[gesichtete Version][gesichtete Version]
Inhalt gelöscht Inhalt hinzugefügt
K Änderungen von 2A02:908:2214:A100:98E3:C0FF:FE50:40C0 (Diskussion) auf die letzte Version von Vollbracht zurückgesetzt
Markierung: Zurücksetzung
Keine Bearbeitungszusammenfassung
Zeile 23: Zeile 23:
an object representating whatever is necessary to call a function
an object representating whatever is necessary to call a function
constructors:
constructors:
new(frame) an invoke call of a Test function
new(frame, action, data) testcase
invoker(frame, action, data) testcase callable by invoke
requirer(frame, action, data) testcase callable by require
suggester(frame, test, result) an invoke call of a Test function
suggester(frame, test, result) an invoke call of a Test function
fields:
fields:
frame object copy of the calling objects frame
wikitext string available for invoke only
wikitext string available for invoke only
luatext string
luatext string
modulename string
modulename string
moduleprefix string 'Module' or 'Modul'
moduleprefix string 'Module' or 'Modul'
Module required module by this.moduleprefix:this.modulename
functionpath array of strings
functionpath array of strings
parameters array
parameters array
Zeile 38: Zeile 38:
unwraped luatext with its particular parameters
unwraped luatext with its particular parameters
toCode() returns <code>-wraped toString()
toCode() returns <code>-wraped toString()
getModule() returns required module by this name
extensionTag(element, core, attributes) copy of frames extensionTag
]]
]]
local Call = {
local Call = {
Zeile 49: Zeile 47:
if this.wraper then return this.wraper end
if this.wraper then return this.wraper end
return this.luatext
return this.luatext
end,
getModule = function(this)
local name = this.modulename
if not name or name == '' then return nil end
if this.wikitext and
(not this.moduleprefix or this.moduleprefix == '') then
local _, result = pcall(require, 'Module:' .. name)
if not result then _, result = pcall(require, 'Modul:' .. name) end
return result
end
if not this.moduleprefix or this.moduleprefix == '' then return nil end
local _, result = pcall(require, this.moduleprefix .. ':' .. name)
return result
end,
end,
toCode = function(this)
toCode = function(this)
Zeile 69: Zeile 54:


--[[
--[[
invoker(frame, action, data)
new(frame, action, data)
constructor for a client call, i. e. a test case
constructor for a client call, i. e. a test case
parameters:
parameters:
Zeile 75: Zeile 60:
action string representing the module and its directly exported
action string representing the module and its directly exported
function, which is to be tested
function, which is to be tested
possible formats:
"<module>/<function path>"
{modulename=<module name>, functionpath=<function path>}
{ moduleprefix=<module prefix>, modulename=<module name>,
functionpath=<function path> }
Call
data all parameters remaining in test case, which are handed over to
data all parameters remaining in test case, which are handed over to
the client module and function that is to be tested
the client module and function that is to be tested
returns the new call object
returns the new call object
]]
]]
function Call:invoker(frame, action, data)
function Call:new(frame, action, data)
if not frame or not frame.extensionTag or not action then return nil end
if not frame or not frame.extensionTag or not action then return nil end
if type(action) == 'string' then
action = mw.text.split(action, '$s*%/%s*')
end
if type(action) ~= 'table' or #action < 2 then return nil end
local result = {
local result = {
frame = frame,
frame = frame,
functionpath = { action[2] },
parameters = data
parameters = data
}
}
local set, success, extendSearch = false
result.moduleprefix, result.modulename = action[1]:match('(Modul[e]?):(.*)')
if type(action) == 'string' then
if not result.modulename then result.modulename = action[1] end
result.Module = nil
result.functionpath = {}
set = {action}
if not action:find('^Modul') then extendSearch = true end
while not result.Module do
mw.log(set[1])
set = {set[1]:match('(.*)%/(%a%w*)$')}
mw.logObject(set, 'set is')
if not set[1] then return nil end
result.functionpath = {set[2], unpack(result.functionpath)}
success, result.Module = pcall(require, set[1])
if not success and extendSearch then
success, result.Module = pcall(require, 'Modul:' .. set[1])
if result.Module then
result.modulename = set[1]
result.moduleprefix = 'Modul:'
else
success, result.Module = pcall(require, 'Module:' .. set[1])
if result.Module then
result.modulename = set[1]
result.moduleprefix = 'Module:'
end
end
end
end
elseif type(action) == 'table' then
if not action.modulename then return nil end
if not action.functionpath then return nil end
result.modulename = action.modulename
result.functionpath = functionpath
if action.Module then
result.Module = action.Module
if action.moduleprefix then
result.moduleprefix = action.moduleprefix
end
else
if action.moduleprefix then
result.moduleprefix = action.moduleprefix
success, result.Module = pcall(require,
result.moduleprefix .. result.modulename)
if not success then return nil end
else
success, result.Module = pcall(require, result.modulename)
if not success then
local mn = 'Modul:' .. result.modulename
success, result.Module = pcall(require, mn)
if success then result.moduleprefix = 'Modul:'
else
local mn = 'Module:' .. result.modulename
success, result.Module = pcall(require, mn)
if success then result.moduleprefix = 'Module:'
else return nil end
end
end
end
end
end
local luadata = ''
local luadata = ''
local wtData = ''
local wtData = ''
Zeile 103: Zeile 146:
end
end
if luadata == '' then
if luadata == '' then
result.luatext = action[2] .. '()'
result.luatext = table.concat(result.functionpath, '/') .. '()'
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
if #result.functionpath == 1 then
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
.. action[2] .. '}}'
.. result.functionpath[1] .. '}}'
end
else
else
result.luatext = action[2] .. '{' .. luadata:sub(3) .. '}'
result.luatext = table.concat(result.functionpath, '/') .. '{'
.. luadata:sub(3) .. '}'
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
if #result.functionpath == 1 then
.. action[2] .. wtData:gsub('%s*$', '') .. '}}'
result.wikitext = '{{#invoke:' .. result.modulename .. ' |'
.. result.functionpath[1]
.. wtData:gsub('%s*$', '') .. '}}'
end
end
end
setmetatable(result, self)
setmetatable(result, self)
Zeile 116: Zeile 165:
end
end


--[[
requirer(frame, action, data)
constructor for a client call, i. e. a test case
parameters:
frame environmental frame of the test providing call
action string representing the module and its directly exported
function, which is to be tested
data all parameters remaining in test case, which are handed over to
the client module and function that is to be tested
returns the new call object
]]
function Call:requirer(frame, action, data)
if not frame or not frame.extensionTag or not action then return nil end
if type(action) == 'string' then
action = mw.text.split(action, '$s*%/%s*')
end
if type(action) ~= 'table' or #action < 2 then return nil end
local result = {
frame = frame,
functionpath = {},
parameters = data,
wikitext = nil
}
for i = 2, #action do table.insert(result.functionpath, action[i]) end
result.moduleprefix, result.modulename = action[1]:match('(Modul[e]?):(.*)')
if not result.modulename then result.modulename = action[1] end
local luadata = ''
for i, v in ipairs(data) do
luadata = luadata .. ', [' .. i .. '] = "' .. v .. '"'
end
for k, v in ipairs(data) do
luadata = luadata .. ', ' .. k .. ' = "' .. v .. '"'
end
if luadata == '' then
result.luatext = action[2] .. '()'
else
result.luatext = action[2] .. '{' .. luadata:sub(3) .. '}'
end
setmetatable(result, self)
self.__index = self
return result
end


--[[
--[[
Zeile 364: Zeile 371:
}
}
end
end
action = mw.text.split(action, '%/')
-- 2.: generate string representation from action
-- 2.: generate string representation from action
result.call = Call:new(frame, action, data)
local e = ""
local e = ''
if #action > 2 then
result.call = Call:requirer(frame, action, data)
if not result.call then
e = 'Inappropriate argument "action" is "' .. action
elseif #action == 2 then
result.call = Call:invoker(frame, action, data)
else
e = 'Inappropriate argument "action" is "' .. action[1]
.. '" but should be "<module name>/<function name>" instead.'
.. '" but should be "<module name>/<function name>" instead.'
return { Error = e }
return { Error = e }
Zeile 378: Zeile 381:
mw.logObject(result, 'result')
mw.logObject(result, 'result')
-- 3.: retreive executable function from action
-- 3.: retreive executable function from action
local Function = result.call:getModule()
local Function = result.call.Module
if not Function then
if not Function then
e = 'Inappropriate module name "' .. result.call.modulename
e = 'Inappropriate module name "' .. result.call.modulename

Version vom 26. Dezember 2022, 20:09 Uhr

Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus


--[=[  Test 2022-12-10
	test a module
	
	Autor: Vollbracht
	
* test.single(frame)
  {{#invoke:Test|single	|module=<module name> |expected=<html or wikitext>
						|func=<function to be tested>
						|<key=value> |<key=value> ... }}
  {{#invoke:Test|single	|module=<module name> |expected=<html or wikitext>
						|func=<function to be tested>
						|unnamed=<value>{!}<value>{!} ... }}
]=]

local p = {}

local USAGE =
[[<code>{{#invoke:Test|single|module=<module name> |expected=<html or wikitext>
|func=<function to be tested> |<key=value> |<key=value> ... }}</code>]]

--[[
	Call
	an object representating whatever is necessary to call a function
	constructors:
		new(frame, action, data)		testcase
		suggester(frame, test, result)	an invoke call of a Test function
	fields:
		frame			object	copy of the calling objects frame
		wikitext		string	available for invoke only
		luatext			string
		modulename		string
		moduleprefix	string	'Module' or 'Modul'
		Module			required module by this.moduleprefix:this.modulename
		functionpath	array of strings
		parameters		array
	methods:
		toString()		returns <nowiki>-wraped wikitext or (if inavailable)
						unwraped luatext with its particular parameters
		toCode()		returns <code>-wraped toString()
]]
local Call = {
	properties = {module=1, func=2, expected=4, unnamed=8, export=16},
	toString = function(this) -- ####
		if this.wikitext then
			return this.frame:extensionTag('nowiki', this.wikitext, {})
		end
		if this.wraper then return this.wraper end
		return this.luatext
	end,
	toCode = function(this)
		return this.frame:extensionTag('code', this:toString(), {})
	end,
}

--[[
	new(frame, action, data)
	constructor for a client call, i. e. a test case
	parameters:
		frame	environmental frame of the test providing call
		action	string representing the module and its directly exported
				function, which is to be tested
				possible formats:
					"<module>/<function path>"
					{modulename=<module name>, functionpath=<function path>}
					{	moduleprefix=<module prefix>, modulename=<module name>,
						functionpath=<function path>	}
					Call
		data	all parameters remaining in test case, which are handed over to
				the client module and function that is to be tested
	returns the new call object
]]
function Call:new(frame, action, data)
	if not frame or not frame.extensionTag or not action then return nil end
	local result = {
		frame = frame,
		parameters = data
	}
	local set, success, extendSearch = false
	if type(action) == 'string' then
		result.Module = nil
		result.functionpath = {}
		set = {action}
		if not action:find('^Modul') then extendSearch = true end
		while not result.Module do
			mw.log(set[1])
			set = {set[1]:match('(.*)%/(%a%w*)$')}
			mw.logObject(set, 'set is')
			if not set[1] then return nil end
			result.functionpath = {set[2], unpack(result.functionpath)}
			success, result.Module = pcall(require, set[1])
			if not success and extendSearch then
				success, result.Module = pcall(require, 'Modul:' .. set[1])
				if result.Module then
					result.modulename = set[1]
					result.moduleprefix = 'Modul:'
				else
					success, result.Module = pcall(require, 'Module:' .. set[1])
					if result.Module then
						result.modulename = set[1]
						result.moduleprefix = 'Module:'
					end
				end
			end
		end
	elseif type(action) == 'table' then
		if not action.modulename then return nil end
		if not action.functionpath then return nil end
		result.modulename = action.modulename
		result.functionpath = functionpath
		if action.Module then
			result.Module = action.Module
			if action.moduleprefix then
				result.moduleprefix = action.moduleprefix
			end
		else
			if action.moduleprefix then
				result.moduleprefix = action.moduleprefix
				success, result.Module = pcall(require,
									result.moduleprefix .. result.modulename)
				if not success then return nil end
			else
				success, result.Module = pcall(require, result.modulename)
				if not success then
					local mn = 'Modul:' .. result.modulename
					success, result.Module = pcall(require, mn)
					if success then result.moduleprefix = 'Modul:'
					else
						local mn = 'Module:' .. result.modulename
						success, result.Module = pcall(require, mn)
						if success then result.moduleprefix = 'Module:'
						else return nil end
					end
				end
			end
		end
	end
	local luadata = ''
	local wtData = ''
	for i, v in ipairs(data) do
		luadata = luadata .. ', [' .. i .. '] = "' .. v .. '"'
		wtData = wtData .. '|' .. v
	end
	for k, v in pairs(data) do
		luadata = luadata .. ', ' .. k .. ' = "' .. v .. '"'
		wtData = wtData .. '|' .. k .. '=' .. v .. ' '
	end
	if luadata == '' then
		result.luatext =	table.concat(result.functionpath, '/') .. '()'
		if #result.functionpath == 1 then
			result.wikitext =	'{{#invoke:' .. result.modulename .. ' |'
							..	result.functionpath[1] .. '}}'
		end
	else
		result.luatext =	table.concat(result.functionpath, '/') .. '{'
						..	luadata:sub(3) .. '}'
		if #result.functionpath == 1 then
			result.wikitext =	'{{#invoke:' .. result.modulename .. ' |'
							..	result.functionpath[1]
							..	wtData:gsub('%s*$', '') .. '}}'
		end
	end
	setmetatable(result, self)
	self.__index = self
	return result
end


--[[
	suggester(frame, test, suggestion)
	constructor for a test call
	parameters:
		frame	environmental frame of the test providing call
		test	name of the used test function in Test module
		suggestion	suggested result for the tested function
	returns the new call object
]]
function Call:suggester(frame, test, suggestion)
	local result = {
		frame = frame,
		modulename = 'Test',
		functionpath = { test },
		parameters = frame.args
	}
	local luadata = ''
	local wtData = ''
	for i, v in ipairs(frame.args) do
		luadata = luadata .. ', [' .. i .. '] = "' .. v .. '"'
		wtData = wtData .. '|' .. v
	end
	for k, v in pairs(frame.args) do
		luadata = luadata .. ', ' .. k .. ' = "' .. v .. '"'
		wtData = wtData .. '|' .. k .. '=' .. v .. ' '
	end
	luadata = luadata .. ', expected = "' .. suggestion .. '"'
	wtData = wtData .. '|expected=' .. suggestion
	result.luatext = test .. '{' .. luadata:sub(3) .. '}'
	result.wikitext =	'{{#invoke:Test |' .. test .. ' ' .. wtData .. ' }}'
	setmetatable(result, self)
	self.__index = self
	return result
end

--[[
	Counter
	Object: value set of prefix, number and postfix with an optional key name
	
]]
local Counter = {
	use = function(this)
		local result = this.prefix .. tostring(this.value) .. this.postfix
		this.value = this.value + 1
		return result
	end,
	keyValue = function(this, index)
		return index + 1, this.prefix .. index .. this.postfix
	end
}
function Counter:new(source, key)
	if not source then return nil end
	local result = {}
	if key then result.key = key end
	if type(source) == 'string' then
		result.prefix, result.value, result.postfix
			= source:match('(.*)%<counter>(%d*)%<%/counter%>(.*)')
		if result.value then
			if result.value == '' then result.value = 0
			else result.value = tonumber(result.value) end
		else
			result.prefix, result.postfix
				= source:match('(.*)%<counter%s?%/%>(.*)')
			if not result.prefix then return nil end
			result.value = 0
		end
	end
	if type(source) == 'table' then result = source end
	if not result.prefix or not result.value or not result.postfix then
		return nil
	end
	setmetatable(result, self)
	self.__index = self
	return result
end

--[[
	Client
	a test type object
	constructor:
		new(frame)
	fields:
		call		a Call object
		expected	string for comparison
		actual		string or an object with a __tostring method
		(	Following fields are for internal use only, because they aren't
			balanced. Use get<Field>() instead!	)
		accordance1	wikitext - do not get
		accordance2	wikitext - do not get
		missedMark	wikitext - get includes accordances
		variation	wikitext - get includes accordances
	methods:
		getVariation()	colored wikitext output of variation
						including accordances
		getMissedMark()	colored wikitext output of missedMark
						including accordances
]]
local Client = {
	getVariation = function(this)
		if not this.variation then return '' end
		local result = this.accordance1
		if result then
			if result ~= '' then
				result = this.call.frame:extensionTag('span',
						this.call.frame:extensionTag('nowiki', result, {}),
						{ style="background-color:#bfa;" })
			end
		else result = '' end
		result = result
			..	this.call.frame:extensionTag('span',
					this.call.frame:extensionTag('nowiki', this.variation, {}),
				{ style="background-color:#fba;" })
		if not this.accordance2 or this.accordance2 == '' then
			return this.call.frame:extensionTag('code', result, {}) end
		return this.call.frame:extensionTag('code', result
			..	this.call.frame:extensionTag('span',
					this.call.frame:extensionTag(	'nowiki', this.accordance2,
													{}),
				{ style="background-color:#bfa;" }), {})
	end,
	getMissedMark = function(this)
		if not this.missedMark or this.missedMark == '' then
			return this.call.frame:extensionTag('code', 
				this.call.frame:extensionTag('span',
				this.call.frame:extensionTag('nowiki', this.expected, {}),
				{ style="background-color:#bfa;" }), {})
		end
		local result = this.accordance1
		if result then
			if result ~= '' then
				result = this.call.frame:extensionTag('span',
					this.call.frame:extensionTag('nowiki', result, {}),
					{ style="background-color:#bfa;" })
			end
		else result = '' end
		result =	result
			..	this.call.frame:extensionTag('nowiki', this.missedMark, {})
		if not this.accordance2 or this.accordance2 == '' then
			return this.call.frame:extensionTag('code', result, {}) end
		return this.call.frame:extensionTag('code', result
			..	this.call.frame:extensionTag('span',
					this.call.frame:extensionTag(	'nowiki', this.accordance2,
													{}),
				{ style="background-color:#bfa;" }), {})
	end,
	toString = function(this) -- ####
		if this.variation then return this:getVariation() end
		return this.actual
	end
}

function Client:new(frame)
	local result = {}
	local data = {}
	if #frame.args > 0 then data = {unpack(frame.args)} end
	local action = nil
	-- 1.: unwrap action and expectation from frame.args
	for k, v in pairs(frame.args) do
		if k == 'action' then
			-- extract 1st action only; actions may be delimited by '//'
			v = v:gsub('^%/*', '')
			local i = v:find('//')
			if i then
				action = v:sub(1, i-1)
				if #v > i+2 then v = v:sub(i+2)
				else v = nil end
			else
				action = v
				v = nil
			end
		elseif k == 'expected' then
			-- extract 1st expectation only;
			-- expectations may be delimited by '<split></split>'
			local i, j = v:find('.%<split%>%<%/split%>.')
			if i then
				result.expected = v:sub(1, i - 1)
				if #v > j+1 then v = v:sub(j + 1)
				else v = nil end
			else
				result.expected = v
				v = nil
			end
		else
			local s, c = v:gsub('%<inTestSeries%s*%>?%<?%/%>?', '')
			if c > 0 then
				s = s:gsub('inTestSeries%>', '')
				if not result.testSeries then result.testSeries = {} end
				table.insert(result.testSeries, k)
				v = s
			elseif v:find('%<counter') then
				if not result.counters then result.counters = {} end
				local c = Counter:new(v, k)
				if c then
					table.insert(result.counters, c)
					v = c:use()
				end
			end
		end
		if v then data[k] = v end
	end
	if not action then
		return {
			Error = 'Missing argument "action=<module name>/<function name>".'
		}
	end
	-- 2.: generate string representation from action
	result.call = Call:new(frame, action, data)
	local e = ''
	if not result.call then
		e =	'Inappropriate argument "action" is "' .. action
		..	'" but should be "<module name>/<function name>" instead.'
		return { Error = e }
	end
	mw.logObject(result, 'result')
	-- 3.: retreive executable function from action
	local Function = result.call.Module
	if not Function then
		e =	'Inappropriate module name "' .. result.call.modulename
		..	'" in test call.'
		if not result.call.moduleprefix then
			if result.call.wikitext then
				e = e .. ' (Even with "Module:" or "Modul:" prefix!)'
			else
				e = e .. ' Try providing a "Module:" or "Modul:" prefix!'
			end
		end
		return { Error = e }
	end
	for _, v in ipairs(result.call.functionpath) do
		Function = Function[v]
		if not Function then
			e =	'Inavailable export "' .. v
			..	'" in ' .. result.call.luatext .. ' call.'
			return { Error = e }
		end
	end
	-- 4.: get actual result of execution of this function
	local handler = function(err)
		e = err
		mw.logObject(e, 'error in execution of ' .. result.call.luatext)
	end
	local success = false
	if result.call.wikitext then
		mw.logObject(result, 'try wikitext')
		local c = result.call
		local f =  frame:newChild({ title = c.modulename, args=c.parameters })
		fkt = function()
			return Function(f)
		end
		success, result.actual = xpcall(fkt, handler, {})
	end
	if not success then
		mw.logObject(result, 'try without wikitext')
		success, result.actual = xpcall(Function, handler, unpack(data))
	end
	if not success or e and e ~= '' then return {Error = e} end
	-- 5.: tostring actual result
	if type(result.actual) == 'table' and result.actual.toString then
		result.actual = result.actual:toString()
	elseif type(result.actual) ~= 'string' then
		handler = function(err)
			e = err
			mw.logObject(e, 'tostring error in result of '
						..	action[#action] .. ' function')
		end
		fkt = function()
			return tostring(result.actual)
		end
		success, result.actual = xpcall(fkt, handler, {})
		if not success or e and e ~= '' then return {Error = e} end
	end
	-- 6.: get dif
	mw.logObject(result, 'client at get dif')
	if not result.expected then
		
	elseif result.actual == result.expected then
		result.accordance1 = result.actual
	else
		local aList = mw.text.split(result.actual, '')
		local eList = mw.text.split(result.expected, '')
		local i = 1
		result.accordance1 = ""
		while i <= #aList and i <= #eList and aList[i] == eList[i] do
			result.accordance1 = result.accordance1 .. aList[i]
			i = i + 1
		end
		result.accordance1 = result.accordance1:gsub('&', '&amp;')
		local ia = #aList
		local ie = #eList
		result.accordance2 = ""
		while i <= ia and i <= ie and aList[ia] == eList[ie] do
			result.accordance2 = aList[ia] .. result.accordance2
			ia = ia - 1
			ie = ie - 1
		end
		result.accordance2 = result.accordance2:gsub('&', '&amp;')
		if i > ia then
			result.variation = '[..]'
		else
			result.variation = ''
			while i <= ia do
				result.variation = aList[ia] .. result.variation
				ia = ia - 1
			end
		end
		result.variation = result.variation:gsub('&', '&amp;')
		result.missedMark = ''
		while i <= ie do
			result.missedMark = eList[ie] .. result.missedMark
			ie = ie - 1
		end
		result.missedMark = result.missedMark:gsub('&', '&amp;')
	end
	setmetatable(result, self)
	self.__index = self
	return result
end

p.single = function(frame)
	if not frame.args then
		return	'First usage: ' .. frame:extensionTag('syntaxhighlight',
			'{{#invoke:Test|single|action=<module name>/<exported function>|...'
		..	'}}', { lang="html+handlebars"}) .. ' with whatever params your fun'
			..	'ction might need in addition'
	end
	local client = Client:new(frame)
	if client.Error then return client.Error end
	local result =	'<table class="wikitable"><tr><th>call</th><td>'
				..	frame:extensionTag('code', client.call:toString(), {})
				..	'</td></tr><tr><th>actual value</th><td>'
				..	client.actual .. '</td></tr>'
	if not client.expected then
		local call = Call:suggester(frame, 'single', client.actual)
		return	result .. '<tr><td colspan="2">following call whould indica'
			..	'te success:</td></tr><tr><td colspan="2">'
			..	frame:extensionTag('syntaxhighlight', call.wikitext, {
					lang="html+handlebars"
				}) .. '</td></tr></table>'
	end
	if client.variation then
		return	result .. '<tr><th>expected value></th><td>' ..	client.expected
			..	'</td></tr><tr><td colspan="2">' ..	client:getVariation()
			..	'</td></tr><tr><td colspan="2">'
			..	client:getMissedMark() .. '</td></tr></table>'
	end
	return result .. '</table>'
end

p.testTable = function(frame)
	
end

return p