Module:Template test case: Difference between revisions
Jump to navigation
Jump to search
Content added Content deleted
(Don't export the table and nowiki functions. At the moment the Lua interfaces don't make much sense, and rewriting the module to include nice Lua interfaces would probably be a lot of wasted work, as Lua modules will generally use Lua-based test cases.) |
m (87 revisions imported from wikipedia:Module:Template_test_case) |
||
(42 intermediate revisions by 11 users not shown) | |||
Line 1: | Line 1: | ||
--[[ |
|||
-- This module provides several methods to generate test cases. |
|||
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 |
-- Load required modules |
||
Line 33: | Line 54: | ||
getFullPage = true, |
getFullPage = true, |
||
getName = true, |
getName = true, |
||
makeHeader = true, |
|||
getOutput = true |
getOutput = true |
||
} |
} |
||
Line 73: | Line 94: | ||
function Template:getFullPage() |
function Template:getFullPage() |
||
if self.template then |
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) |
local strippedTemplate, hasColon = self.template:gsub('^:', '', 1) |
||
hasColon = hasColon > 0 |
hasColon = hasColon > 0 |
||
Line 85: | Line 110: | ||
return mw.site.namespaces[10].name .. ':' .. strippedTemplate |
return mw.site.namespaces[10].name .. ':' .. strippedTemplate |
||
end |
end |
||
else |
|||
return self.title.prefixedText |
|||
end |
end |
||
end |
end |
||
Line 112: | Line 135: | ||
end |
end |
||
function Template: |
function Template:makeHeader() |
||
return self.heading or self:makeBraceLink() |
return self.heading or self:makeBraceLink() |
||
end |
end |
||
function Template:getInvocation(format) |
function Template:getInvocation(format) |
||
local invocation = self._invocation:getInvocation |
local invocation = self._invocation:getInvocation{ |
||
template = self:getName(), |
|||
requireMagicWord = self.requireMagicWord, |
|||
} |
|||
if format == 'code' then |
if format == 'code' then |
||
invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>' |
invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>' |
||
elseif format == 'kbd' then |
|||
invocation = '<kbd>' .. mw.text.nowiki(invocation) .. '</kbd>' |
|||
elseif format == 'plain' then |
elseif format == 'plain' then |
||
invocation = mw.text.nowiki(invocation) |
invocation = mw.text.nowiki(invocation) |
||
Line 132: | Line 160: | ||
function Template:getOutput() |
function Template:getOutput() |
||
local protect = require('Module:Protect') |
|||
return self._invocation:getOutput(self:getName()) |
|||
-- calling self._invocation:getOutput{...} |
|||
return protect(self._invocation.getOutput)(self._invocation, { |
|||
template = self:getName(), |
|||
requireMagicWord = self.requireMagicWord, |
|||
}) |
|||
end |
end |
||
Line 148: | Line 181: | ||
columns = 'renderColumns', |
columns = 'renderColumns', |
||
rows = 'renderRows', |
rows = 'renderRows', |
||
tablerows = 'renderRows', |
|||
inline = 'renderInline', |
|||
cells = 'renderCells', |
|||
default = 'renderDefault' |
default = 'renderDefault' |
||
} |
} |
||
Line 158: | Line 194: | ||
-- numbered, whereas general options are not. |
-- numbered, whereas general options are not. |
||
local generalOptions, templateOptions = {}, {} |
local generalOptions, templateOptions = {}, {} |
||
for k, v in pairs(options) do |
|||
do |
|||
local prefix, num |
|||
local optionNum = {} -- a unique key for option numbers inside templateOptions |
|||
if type(k) == 'string' then |
|||
local rawTemplateOptions = {} |
|||
prefix, num = k:match('^(.-)([1-9][0-9]*)$') |
|||
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) |
|||
rawTemplateOptions[num] = rawTemplateOptions[num] or {} |
|||
rawTemplateOptions[num][prefix] = v |
|||
rawTemplateOptions[num][optionNum] = num -- record for use in error messages |
|||
else |
|||
generalOptions[k] = v |
|||
end |
|||
end |
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) |
|||
rawTemplateOptions[1] = rawTemplateOptions[1] or {} |
|||
generalOptions.showheader = yesno(generalOptions.showheader) ~= false |
|||
rawTemplateOptions[2] = rawTemplateOptions[2] or {} |
|||
generalOptions.showcaption = yesno(generalOptions.showcaption) ~= false |
|||
if rawTemplateOptions[1].template and not rawTemplateOptions[2].template then |
|||
generalOptions.collapsible = yesno(generalOptions.collapsible) |
|||
rawTemplateOptions[2].template = rawTemplateOptions[1].template .. |
|||
generalOptions.notcollapsed = yesno(generalOptions.notcollapsed) |
|||
'/' .. obj.cfg.sandboxSubpage |
|||
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 |
||
end |
|||
if not rawTemplateOptions[1].template then |
|||
rawTemplateOptions[1].title = mw.title.getCurrentTitle().basePageTitle |
|||
-- 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 |
||
end |
|||
if not rawTemplateOptions[2].template then |
|||
rawTemplateOptions[2].title = rawTemplateOptions[1].title:subPageTitle( |
|||
-- Check for missing template names. |
|||
obj.cfg.sandboxSubpage |
|||
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 |
||
end |
|||
-- Compress templateOptions table so we can iterate over it with ipairs. |
|||
-- Remove gaps in the numbered options |
|||
templateOptions = (function (t) |
|||
local nums = {} |
local nums = {} |
||
for num in pairs( |
for num in pairs(t) do |
||
nums[#nums + 1] = num |
nums[#nums + 1] = num |
||
end |
end |
||
table.sort(nums) |
table.sort(nums) |
||
local ret = {} |
|||
for i, num in ipairs(nums) do |
for i, num in ipairs(nums) do |
||
ret[i] = t[num] |
|||
end |
end |
||
return ret |
|||
end)(templateOptions) |
|||
-- Don't require the __TEMPLATENAME__ magic word for nowiki invocations if |
|||
-- Check that there are no missing template options. |
|||
-- there is only one template being output. |
|||
for i = 3, #templateOptions do -- Defaults have already been added for 1 and 2. |
|||
if #templateOptions <= 1 then |
|||
local t = templateOptions[i] |
|||
templateOptions[1].requireMagicWord = false |
|||
if not t.template then |
|||
local num = t[optionNum] |
|||
error(obj:message( |
|||
'missing-template-option-error', |
|||
num, num |
|||
), 2) |
|||
end |
|||
end |
|||
end |
end |
||
mw.logObject(templateOptions) |
|||
-- Set general options |
|||
generalOptions.showcode = yesno(generalOptions.showcode) |
|||
generalOptions.collapsible = yesno(generalOptions.collapsible) |
|||
obj.options = generalOptions |
|||
-- Make the template objects |
-- Make the template objects |
||
obj.templates = {} |
obj.templates = {} |
||
for i, |
for i, options in ipairs(templateOptions) do |
||
table.insert(obj.templates, Template.new(invocationObj, |
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 |
end |
||
Line 246: | Line 327: | ||
local out = obj:getOutput() |
local out = obj:getOutput() |
||
-- Remove the random parts from strip markers. |
-- Remove the random parts from strip markers. |
||
out = out:gsub('( |
out = out:gsub('(\127\'"`UNIQ.-)%-%x+%-(QINU`"\'\127)', '%1%2') |
||
return out |
return out |
||
end |
end |
||
Line 260: | Line 341: | ||
function TestCase:makeCollapsible(s) |
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 isEqual = self:templateOutputIsEqual() |
||
local root = mw.html.create('table') |
local root = mw.html.create('table') |
||
if self.options.wantdiff then |
|||
root |
root |
||
:addClass('collapsible') |
:addClass('mw-collapsible') |
||
if self.options.notcollapsed == false then |
|||
:addClass(isEqual and 'collapsed' or nil) |
|||
root |
|||
:addClass('mw-collapsed') |
|||
end |
|||
root |
|||
:css('background-color', 'transparent') |
|||
:css('width', '100%') |
|||
:css('border', 'solid silver 1px') |
|||
:tag('tr') |
|||
:tag('th') |
|||
:css('background-color', isEqual and 'yellow' or '#90a8ee') |
|||
:wikitext(title) |
|||
:done() |
|||
:done() |
|||
:tag('tr') |
|||
:tag('td') |
|||
:newline() |
|||
:wikitext(s) |
|||
:newline() |
|||
else |
|||
root |
|||
:addClass('mw-collapsible') |
|||
if self.options.notcollapsed == false then |
|||
root |
|||
:addClass('mw-collapsed') |
|||
end |
|||
if self.options.notcollapsed ~= true or false then |
|||
root |
|||
:addClass(isEqual and 'mw-collapsed' or nil) |
|||
end |
|||
root |
|||
:css('background-color', 'transparent') |
:css('background-color', 'transparent') |
||
:css('width', '100%') |
:css('width', '100%') |
||
Line 271: | Line 387: | ||
:tag('th') |
:tag('th') |
||
:css('background-color', isEqual and 'lightgreen' or 'yellow') |
:css('background-color', isEqual and 'lightgreen' or 'yellow') |
||
:wikitext( |
:wikitext(title) |
||
:done() |
:done() |
||
:done() |
:done() |
||
:tag('tr') |
:tag('tr') |
||
:tag('td') |
:tag('td') |
||
:newline() |
|||
:wikitext(s) |
:wikitext(s) |
||
:newline() |
|||
end |
|||
return tostring(root) |
return tostring(root) |
||
end |
end |
||
Line 289: | Line 408: | ||
local tableroot = root:tag('table') |
local tableroot = root:tag('table') |
||
tableroot |
|||
:addClass(self.options.class) |
|||
:cssText(self.options.style) |
|||
:tag('caption') |
|||
:wikitext(self.options.caption or self:message('columns-header')) |
|||
if self.options.showheader then |
|||
-- Headings |
|||
-- Caption |
|||
local headingRow = tableroot:tag('tr') |
|||
if self.options.showcaption then |
|||
tableroot |
|||
-- rowheader is correct here. We need to add another th cell if |
|||
:addClass(self.options.class) |
|||
-- rowheader is set further down, even if heading0 is missing. |
|||
:cssText(self.options.style) |
|||
:tag('caption') |
|||
end |
|||
:wikitext(self.options.caption or self:message('columns-header')) |
|||
local width |
|||
end |
|||
if #self.templates > 0 then |
|||
width = tostring(math.floor(100 / #self.templates)) .. '%' |
|||
-- Headers |
|||
else |
|||
local headerRow = tableroot:tag('tr') |
|||
width = '100%' |
|||
if self.options.rowheader then |
|||
end |
|||
-- rowheader is correct here. We need to add another th cell if |
|||
for i, obj in ipairs(self.templates) do |
|||
-- rowheader is set further down, even if heading0 is missing. |
|||
headingRow |
|||
:tag('th') |
headerRow:tag('th'):wikitext(self.options.heading0) |
||
end |
|||
:css('width', width) |
|||
local width |
|||
:wikitext(obj:makeHeading()) |
|||
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 |
end |
||
Line 325: | Line 450: | ||
-- Template output |
-- Template output |
||
for i, obj in ipairs(self.templates) do |
for i, obj in ipairs(self.templates) do |
||
if self.options.output == 'nowiki+' then |
|||
dataRow:tag('td') |
|||
: |
dataRow:tag('td') |
||
:newline() |
|||
:wikitext(self:getTemplateOutput(obj)) |
|||
:wikitext(self.options. |
: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 |
end |
||
Line 354: | Line 499: | ||
for _, obj in ipairs(self.templates) do |
for _, obj in ipairs(self.templates) do |
||
local dataRow = tableroot:tag('tr') |
|||
-- Build the row HTML |
|||
tableroot |
|||
-- Header |
|||
:tag('tr') |
|||
if self.options.showheader then |
|||
:tag('td') |
|||
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('text-align', 'center') |
||
:css('font-weight', 'bold') |
:css('font-weight', 'bold') |
||
:wikitext(obj: |
:wikitext(obj:makeHeader()) |
||
dataRow = tableroot:tag('tr') |
|||
end |
|||
end |
|||
:tag('tr') |
|||
:tag('td') |
|||
-- Template output |
|||
:newline() |
|||
if self.options.output == 'nowiki+' then |
|||
:wikitext(self:getTemplateOutput(obj)) |
|||
dataRow:tag('td') |
|||
:newline() |
|||
:wikitext(self:getTemplateOutput(obj)) |
|||
:wikitext('<pre style="white-space: pre-wrap;">') |
|||
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj))) |
|||
:wikitext('</pre>') |
|||
elseif self.options.output == 'nowiki' then |
|||
dataRow:tag('td') |
|||
:newline() |
|||
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj))) |
|||
else |
|||
dataRow:tag('td') |
|||
:newline() |
|||
:wikitext(self:getTemplateOutput(obj)) |
|||
end |
|||
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:getTemplateOutput(obj) |
|||
line[#line + 1] = '<pre style="white-space: pre-wrap;">' |
|||
line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj)) |
|||
line[#line + 1] = '</pre>' |
|||
elseif self.options.output == 'nowiki' then |
|||
line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj)) |
|||
else |
|||
line[#line + 1] = self:getTemplateOutput(obj) |
|||
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('<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) |
return tostring(root) |
||
Line 379: | Line 646: | ||
for i, obj in ipairs(self.templates) do |
for i, obj in ipairs(self.templates) do |
||
ret[#ret + 1] = '<div style="clear: both;"></div>' |
ret[#ret + 1] = '<div style="clear: both;"></div>' |
||
if self.options.showheader then |
|||
ret[#ret + 1] = obj:makeBraceLink() |
|||
ret[#ret + 1] = |
ret[#ret + 1] = obj:makeHeader() |
||
end |
|||
if self.options.output == 'nowiki+' then |
|||
ret[#ret + 1] = self:getTemplateOutput(obj) .. '<pre style="white-space: pre-wrap;">' .. mw.text.nowiki(self:getTemplateOutput(obj)) .. '</pre>' |
|||
elseif self.options.output == 'nowiki' then |
|||
ret[#ret + 1] = mw.text.nowiki(self:getTemplateOutput(obj)) |
|||
else |
|||
ret[#ret + 1] = self:getTemplateOutput(obj) |
|||
end |
|||
end |
end |
||
return table.concat(ret, '\n\n') |
return table.concat(ret, '\n\n') |
||
Line 391: | Line 666: | ||
if self.options.collapsible then |
if self.options.collapsible then |
||
ret = self:makeCollapsible(ret) |
ret = self:makeCollapsible(ret) |
||
end |
|||
for cat in pairs(self.categories) do |
|||
ret = ret .. string.format('[[Category:%s]]', cat) |
|||
end |
end |
||
return ret |
return ret |
||
Line 418: | Line 696: | ||
end |
end |
||
function NowikiInvocation:getInvocation( |
function NowikiInvocation:getInvocation(options) |
||
template = template:gsub('%%', '%%%%') -- Escape "%" with "%%" |
local template = options.template:gsub('%%', '%%%%') -- Escape "%" with "%%" |
||
local invocation, count = self.invocation:gsub( |
local invocation, count = self.invocation:gsub( |
||
self.cfg.templateNameMagicWordPattern, |
self.cfg.templateNameMagicWordPattern, |
||
template |
template |
||
) |
) |
||
if count < 1 then |
if options.requireMagicWord ~= false and count < 1 then |
||
error(self:message( |
error(self:message( |
||
'nowiki-magic-word-error', |
'nowiki-magic-word-error', |
||
Line 433: | Line 711: | ||
end |
end |
||
function NowikiInvocation:getOutput( |
function NowikiInvocation:getOutput(options) |
||
local invocation = self:getInvocation( |
local invocation = self:getInvocation(options) |
||
return mw.getCurrentFrame():preprocess(invocation) |
return mw.getCurrentFrame():preprocess(invocation) |
||
end |
end |
||
Line 454: | Line 732: | ||
end |
end |
||
function TableInvocation:getInvocation( |
function TableInvocation:getInvocation(options) |
||
if self.code then |
if self.code then |
||
local nowikiObj = NowikiInvocation(self.code, self.cfg) |
local nowikiObj = NowikiInvocation.new(self.code, self.cfg) |
||
return nowikiObj:getInvocation( |
return nowikiObj:getInvocation(options) |
||
else |
else |
||
return require('Module:Template invocation').invocation( |
return require('Module:Template invocation').invocation( |
||
template, |
options.template, |
||
self.invokeArgs |
self.invokeArgs |
||
) |
) |
||
Line 466: | Line 744: | ||
end |
end |
||
function TableInvocation:getOutput( |
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{ |
return mw.getCurrentFrame():expandTemplate{ |
||
title = template, |
title = options.template, |
||
args = self.invokeArgs |
args = self.invokeArgs |
||
} |
} |
||
Line 514: | Line 798: | ||
cfg = cfg or mw.loadData(DATA_MODULE) |
cfg = cfg or mw.loadData(DATA_MODULE) |
||
local |
local code = args.code or args[1] |
||
local invocationObj = NowikiInvocation.new(code, cfg) |
|||
args.code = nil |
args.code = nil |
||
args[1] = nil |
|||
-- Assume we want to see the code as we already passed it in. |
-- Assume we want to see the code as we already passed it in. |
||
args.showcode = args.showcode or true |
args.showcode = args.showcode or true |
Latest revision as of 23:24, 7 June 2021
Documentation for this module may be created at Module:Template test case/doc
--[[
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|%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
-- Default is pre tags
invocation = mw.text.encode(invocation, '&')
invocation = '<pre style="white-space: pre-wrap;">' .. invocation .. '</pre>'
invocation = mw.getCurrentFrame():preprocess(invocation)
end
return 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\'"`UNIQ.-)%-%x+%-(QINU`"\'\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('table')
if self.options.wantdiff then
root
:addClass('mw-collapsible')
if self.options.notcollapsed == false then
root
:addClass('mw-collapsed')
end
root
:css('background-color', 'transparent')
:css('width', '100%')
:css('border', 'solid silver 1px')
:tag('tr')
:tag('th')
:css('background-color', isEqual and 'yellow' or '#90a8ee')
:wikitext(title)
:done()
:done()
:tag('tr')
:tag('td')
:newline()
:wikitext(s)
:newline()
else
root
:addClass('mw-collapsible')
if self.options.notcollapsed == false then
root
:addClass('mw-collapsed')
end
if self.options.notcollapsed ~= true or false then
root
:addClass(isEqual and 'mw-collapsed' or nil)
end
root
:css('background-color', 'transparent')
:css('width', '100%')
:css('border', 'solid silver 1px')
:tag('tr')
:tag('th')
:css('background-color', isEqual and 'lightgreen' or 'yellow')
:wikitext(title)
:done()
:done()
:tag('tr')
:tag('td')
:newline()
:wikitext(s)
:newline()
end
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('<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')
-- 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:getTemplateOutput(obj))
:wikitext('<pre style="white-space: pre-wrap;">')
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
:wikitext('</pre>')
elseif self.options.output == 'nowiki' then
dataRow:tag('td')
:newline()
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
else
dataRow:tag('td')
:newline()
:wikitext(self:getTemplateOutput(obj))
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:getTemplateOutput(obj)
line[#line + 1] = '<pre style="white-space: pre-wrap;">'
line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))
line[#line + 1] = '</pre>'
elseif self.options.output == 'nowiki' then
line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))
else
line[#line + 1] = self:getTemplateOutput(obj)
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('<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:getTemplateOutput(obj) .. '<pre style="white-space: pre-wrap;">' .. mw.text.nowiki(self:getTemplateOutput(obj)) .. '</pre>'
elseif self.options.output == 'nowiki' then
ret[#ret + 1] = mw.text.nowiki(self:getTemplateOutput(obj))
else
ret[#ret + 1] = self:getTemplateOutput(obj)
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)
local code = args.code or args[1]
local invocationObj = NowikiInvocation.new(code, cfg)
args.code = nil
args[1] = nil
-- Assume we want to see the code as we already passed it in.
args.showcode = args.showcode or true
local testCaseObj = TestCase.new(invocationObj, args, 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