Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Speedy

From Wikipedia, the free encyclopedia

local getArgs = require("Module:Arguments").getArgs
local pageType = require("Module:Pagetype")
local mbox = require("Module:Message box")
local yesno = require("Module:Yesno")
local button = require('Module:Clickable button')
local preview = require('Module:If preview')
local p = {}
local config = mw.loadData('Module:Speedy/config')
local timeAgo = require('Module:Time ago')
----------------------------------------------------------------------------
-- message function from [[Module:Documentation]]
----------------------------------------------------------------------------
local fillStringWithArgs
local function message(cfgKey, valArray, expectType)
	--[[
	-- Gets a message from the cfg table and formats it if appropriate.
	-- The function raises an error if the value from the cfg table is not
	-- of the type expectType. The default type for expectType is 'string'.
	-- If the table valArray is present, strings such as $1, $2 etc. in the
	-- message are substituted with values from the table keys [1], [2] etc.
	-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
	-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
	--]]
	local msg = config.messages[cfgKey]
	expectType = expectType or 'string'
	if type(msg) ~= expectType then
		error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
	end
	if not valArray then
		return msg
	end
	return fillStringWithArgs(msg, valArray)
end

function fillStringWithArgs(text, valArray)
	if not valArray then
		return text
	end

	local function getVal(match)
		match = tonumber(match)
		return valArray[match] or ''
	end

	return mw.ustring.gsub(text, '$([1-9][0-9]*)', getVal) .. ''
end

local function detectParameters(text)
	return text and mw.ustring.find(text, '$([1-9][0-9]*)') and true or false
end

local function makeUnorderedList(array)
	local ul = mw.html.create('ul')
	for k,v in pairs(array) do
		local li = ul:tag('li')
		li:wikitext(v)
		li:done()
	end
	ul:allDone()
	return tostring(ul)
end

local function makeWikiList(array)
	local out = ''
	for k,v in pairs(array) do
		out = out .. '* ' .. v .. '\n'
	end
	return out
end

----------------------------------------------------------------------------
-- Argument processing (from [[Module:Documentation]])
----------------------------------------------------------------------------

local function makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame, {
			valueFunc = function (key, value)
				if type(value) == 'string' then
					value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
					if key == 'heading' or value ~= '' then
						return value
					else
						return nil
					end
				else
					return value
				end
			end
		})
		return p[funcName](args)
	end
end

----------------------------------------------------------------------------
-- Miscellaneous functions related to speedy deletion
----------------------------------------------------------------------------

local function getDeletionEntry(code)
	return config.deletionCodes[code]
end

local function yn(input, default)
	local res = yesno(input, nil)
	if (res == nil) then return default
	else return res end
end

local function processDeletionArgs(iparams)
	local args = {
		deletionReasons = {},
		deletionReasonsNotice = {},
		entries = {},
		numberOfEntries = 0,
		hideButton = true,
		highestMessage = 0,
		drv = false,
		willProvide = false,
		hide = false,
		blank = false
	}
	local entry = nil
	local replaceParams = false
	local params = {iparams.page or mw.title.getCurrentTitle().fullText}
	local paramNo = 2
	local skipped = true
	local function cleanupLeftover(v)
		if entry then
			table.insert(args.deletionReasons, '<span style="font-style:normal;">' .. fillStringWithArgs(entry.description, params) .. '.</span> ' .. (entry.more and '<span style="font-weight:normal;"> ' .. entry.more .. '</span> ' or '') .. '<span style="font-style:normal;">' .. message('deleteIntroCriteriaLink', {entry.code}) .. '.</span>')
			table.insert(args.deletionReasonsNotice,  fillStringWithArgs(entry.description, params) .. ' ([[WP:CSD#' .. entry.code .. '|CSD ' .. entry.code .. ']]). ' .. (entry.additionalMessage and fillStringWithArgs(entry.additionalMessage, params) or ''))
			table.insert(args.entries, entry)
		else
			if (v ~= '') then
				table.insert(args.deletionReasons, v)
				table.insert(args.deletionReasonsNotice, v)
			end
			table.insert(args.entries, {})
		end
		params = {iparams.page or mw.title.getCurrentTitle().fullText}
		paramNo = 2
		args.customHeader = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customHeader or args.customHeader
		args.customIntro = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customIntro or args.customIntroDeleted
		args.customIntroDeleted = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customIntroDeleted or args.customIntroDeleted
		args.customCloser = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customCloser or args.customCloser
		args.numberOfEntries = args.numberOfEntries + 1
		args.hideButton = entry and (args.hideButton and (entry.notice or 2) == 0) or false
		args.highestMessage = entry and ((entry.notice or 2) >= args.highestMessage) and entry.notice or args.highestMessage
		args.drv = entry and (args.drv or entry.drv) or args.drv
		args.willProvide = entry and entry.willProvide or args.willProvide
		args.hide = entry and (entry.hide or args.hide) or args.hide
		args.blank = entry and (entry.blank or args.blank) or args.blank
	end
	for k,v in ipairs(iparams) do
		if type(k) == type(1) then
			skipped = false
			if (replaceParams) then
				local pName = fillStringWithArgs(entry and entry.inputFormat[paramNo - 1] or '$2', {iparams.page or mw.title.getCurrentTitle().fullText, v})
				paramNo = paramNo + 1
				table.insert(params, pName)
			else 
				entry = getDeletionEntry(v) or nil
				replaceParams = entry and detectParameters(entry.description) or false
			end
			if not replaceParams then
				cleanupLeftover(v)
			end
		end
	end
	if replaceParams then
		cleanupLeftover('')
	end
	if skipped then
		args.hideButton = false
	end
	args.help = yn(iparams.help, true)
	args.nocat = yn(iparams.nocat, false)
	args.bot = yn(iparams.bot, false)
	args.noHeader = yn(iparams.noheader, false)
	args.additionalNote = iparams.additionalnote
	args.pageName = iparams.page
	args.notice = yn(iparams.notice, false)
	args.date = iparams.date or mw.getCurrentFrame():preprocess('{{safesubst:REVISIONTIMESTAMP}}')
	return args
end

local isSubstituted = mw.isSubsting;

----------------------------------------------------------------------------
-- Entry point
----------------------------------------------------------------------------

p.main = makeInvokeFunc('_main')
function p._main(params)
	-- get page 
	local args = processDeletionArgs(params)
	local out = ''
	if args.notice then
		-- we are handling a note for the talk page, not a note for deletion
		if not args.pageName then
			return preview._warning({'No page name specified. Proceeding will do nothing.'})
		end
		if not args.noHeader then
			out = out .. (args.customHeader and '== ' .. fillStringWithArgs(args.customHeader, {args.pageName}) .. ' ==' or "== " .. message("noticeHeader", {args.pageName}) .. " ==") .. '\n'
		end
		local messageType = args.highestMessage == 1 and 'welcome' or 'notice'
		out = out .. (message('level' .. args.highestMessage .. 'icon') == '' and '' or '[[' .. message('level' .. args.highestMessage .. 'icon') .. '|40px]] ')
		if mw.title.new(args.pageName).exists then
			-- if the page exists then show the "page may be deleted" message.
			if args.customIntro then
				out = out .. fillStringWithArgs(args.customIntro, {
					args.pageName,
					args.deletionReasonsNotice[1]
				}) .. '\n\n'
			elseif args.numberOfEntries == 1 then
				out = out .. message(messageType .. 'Message', {
					args.pageName,
					args.deletionReasonsNotice[1]
				}) .. '\n\n'
			elseif args.numberOfEntries > 1 then
				out = out .. message(messageType .. 'MessageMultiple', {
					args.pageName,
					'\n' .. makeWikiList(args.deletionReasonsNotice)
				}) .. '\n'
			else
				out = out .. message(messageType .. 'MessageMultiple', {
					args.pageName,
					message("seePageForWhy")
				}) .. '\n\n'
			end
			if args.customCloser then
				out = out .. args.customCloser
			else 
				-- this ifexist checks if the page has possibly been deleted or moved
				-- ifexist is expensive so it may return false from time to time, even if the page exists
				-- fortunately this is handled in the message by saying "appears" rather than "has been"
				out = out .. '{{#ifexist:' .. args.pageName .. '|'
				if args.hideButton then
					out = out .. message("removeSpeedyMessage", {args.pageName}) .. ' '
				else
					out = out .. message("contestMessage", {args.pageName, '{{button|' .. message("contestButton") .. '}}'}) .. ' '
					out = out .. message('closingWarning', {message('removeSpeedyWarning')}) .. ' '
				end
				if args.willProvide then out = out .. message('undeleteSuggestion', {message('pageIsDeleted'), args.pageName, message(args.drv and 'requestDeletionReview' or 'requestUndeletion')}) end
				out = out .. '|'
				out = out .. message('deletedAfterMessage') .. ' '
				if not args.hideButton then out = out .. message('closingWarning', {message('recreateWarning')}) .. ' ' end
				if args.willProvide then out = out .. message('undeleteSuggestion', {message('pageShouldNotHaveBeenDeleted'), args.pageName, message(args.drv and 'requestDeletionReview' or 'requestUndeletion')}) end
				out = out .. '}}'
			end
		else
			-- if the page does not exist then show the "page has been deleted" message
			if args.customIntroDeleted then
				out = out .. fillStringWithArgs(args.customIntroDeleted, {
					args.pageName,
					args.deletionReasonsNotice[1]
				}) .. '\n\n'
			elseif args.numberOfEntries == 1 then
				out = out .. message(messageType .. 'MessageDeleted', {
					args.pageName,
					args.deletionReasonsNotice[1]
				}) .. '\n\n'
			elseif args.numberOfEntries > 1 then
				out = out .. message(messageType .. 'MessageDeletedMultiple', {
					args.pageName,
					'\n' .. makeWikiList(args.deletionReasonsNotice)
				}) .. '\n\n'
			else
				out = out .. message(messageType .. 'MessageDeletedMultiple', {
					args.pageName,
					message("seePageForWhy")
				}) .. '\n\n'
			end
			if args.customCloser then
				out = out .. args.customCloser
			else 
				out = out .. message('deletedAfterMessage')
				if not args.hideButton then out = out .. message('closingWarning', {message('recreateWarning')}) .. ' ' end
				if args.willProvide then out = out .. message('undeleteSuggestion', {message('pageShouldNotHaveBeenDeleted'), args.pageName, message(args.drv and 'requestDeletionReview' or 'requestUndeletion')}) end
			end
		end
		
		return mw.getCurrentFrame():preprocess(out)
	else
		-- we are handling a deletion template message
		if isSubstituted() then
			-- if substituted then just prefill parameters
			-- simpler than the unsubst module because this just autofills the parameters then returns the text needed for the specific deletion template
			local out = '{{db/sandbox' -- to be changed when module is fully implemented
			for k,v in ipairs(params) do
				out = out .. '|' .. v
			end
			out = out .. (args.help and '' or '|help=off')
			out = out .. (args.nocat and '|nocat=yes' or '')
			out = out .. (args.bot and '|bot=yes' or '')
			out = out .. (args.additionalNote and '|additionalnote=' .. args.additionalNote or '')
			out = out .. (args.date and '|date=' .. args.date or '{{safesubst:REVISIONTIMESTAMP}}')
			out = out .. '}}'
			return out
		else
			local titleOfPage = mw.title.getCurrentTitle()
			local pt = pageType._main({page = titleOfPage.fullText})
			local introPrefixToUse = args.bot and 'bot' or 'delete'
			local mainNotice = mw.html.create('div')
			mainNotice:wikitext('\n')
			local intro = mw.html.create('span')
			intro:css{["font-style"] = "italic", ["font-weight"] = "bold"}
			if args.numberOfEntries == 1 then
				intro:wikitext(message(introPrefixToUse .. 'Intro', {
					pt,
					args.deletionReasons[1]
				}))
			elseif args.numberOfEntries > 1 then
				intro:wikitext(message(introPrefixToUse .. 'IntroMultiple', {
					pt,
					makeUnorderedList(args.deletionReasons)
				}))
			else
				intro:wikitext(message(introPrefixToUse .. 'IntroMultiple', {
					pt,
					message("noReasonWarning")
				}))
			end
			intro:allDone()
			mainNotice:wikitext(tostring(intro))
			if args.additionalNote then
				mainNotice:wikitext('\n\n' .. message('additionalNote', {
					args.additionalNote
				}))
			end
			if args.hideButton then
				mainNotice:wikitext('\n\n' .. message("removeNoticeNoButton", {pt})):allDone()
			else
				mainNotice:wikitext('\n\n' .. 
					message("removeNotice", {
						pt,
						message('removeNoticeWarning', {
							titleOfPage.isTalkPage and message('checkBelow') or '[[' .. titleOfPage.talkPageTitle.fullText .. '|' .. message('visitTheTalkPage') .. ']]'
						})
					})
				):done()
				local contestButton = mw.html.create('div')
				contestButton:css{['margin-left'] = 'auto', ['margin-right'] = 'auto', ['text-align'] = 'center'}
				if args.numberOfEntries == 1 then
					contestButton:wikitext(button.main({
						message("contestButton"),
						class="mw-ui-progressive",
						url="{{fullurl:" .. titleOfPage.talkPageTitle.fullText .. '|action=edit&section=new&preloadtitle={{urlencode:' .. message('contestPreloadTitle') .. '}}&preload={{urlencode:' .. (mw.title.new(message('contestPreload', {args.entries[1].code})).exists and message('contestPreload', {args.entries[1].code}) or message('contestPreloadGeneric')) .. '}}&editintro={{urlencode:' .. message('contestPreloadEditintro') .. '}}}}'
					})):done()
				else
					contestButton:wikitext(button.main({
						message("contestButton"),
						class="mw-ui-progressive",
						style="text-align:center;",
						url="{{fullurl:" .. titleOfPage.talkPageTitle.fullText .. '|action=edit&section=new&preload={{urlencode:' .. message('contestPreloadGeneric') .. '}}&editintro={{urlencode:' .. message('contestPreloadEditintro') .. '}}}}'
					})):done()
				end
				mainNotice:wikitext('\n\n' .. tostring(contestButton))
				mainNotice:wikitext('\n\n' .. message("deleteCloser", {
					pt,
					titleOfPage.isTalkPage and message("deleteCloserProvidedBelowNotice") or message("deleteCloserProvidedOnTalkPage")
				})):done()
				if (args.help) then
					local templateCall = '<code><nowiki>{{subst:db|page=' .. titleOfPage.fullText
					for k,v in pairs(params) do
						templateCall = templateCall .. '|' .. k .. '=' .. v
					end
					templateCall = templateCall .. '|notice=yes}} ~~' .. '~~</nowiki></code>'
					mainNotice:wikitext('\n\n' .. message('deleteNoticeTemplate', {templateCall}))
				end
				local hangOn = mw.html.create('span')
				if titleOfPage.talkPageTitle.exists then
					hangOn:addClass('sysop-show')
					hangOn:wikitext( 
						message("hangOnAdmin", {
							pt,
							titleOfPage.isTalkPage and message("checkBelow") or message("hangOnTalkPage")
							
						})
					):done()
				else
					hangOn:wikitext(message('hangOn', {pt})):done()
				end
				mainNotice:wikitext('\n\n' .. tostring(hangOn))
			end
			if args.numberOfEntries == 1 then
				if args.entries[1].notes then
					mainNotice:wikitext(args.entries[1].notes):done()
				end
			end
			local deleteReasonSummary = ''
			if args.numberOfEntries > 1 then
				deleteReasonSummary = 'Multiple criteria: '
				local isFirst = true
				for k,v in pairs(args.entries) do
					deleteReasonSummary = v.code and deleteReasonSummary .. (isFirst and '' or ', ') .. '[[WP:CSD#' .. v.code .. '|' .. v.code .. ']]' or ''
					isFirst = false
				end
			elseif args.numberOfEntries == 1 then
				deleteReasonSummary = args.entries[1].code and deleteReasonSummary .. '[[WP:CSD#' .. args.entries[1].code .. '|' .. args.entries[1].code .. ']]' or ''
			end
			if deleteReasonSummary == '' then
				deleteReasonSummary = '[[WP:CSD|Speedy]]'	
			end
			local adminMessage = mw.html.create('span')
			adminMessage:addClass('sysop-show')
			adminMessage:wikitext(
				message(args.bot and 'checkBot' or 'check', 
					{args.bot and message('check', {deleteReasonSummary}) or deleteReasonSummary}
				)
			):done()
			mainNotice:wikitext('\n\n' .. tostring(adminMessage))
			local lastEditUser = mw.getCurrentFrame():callParserFunction('REVISIONUSER', titleOfPage.fullText)
			local editDate = mw.getCurrentFrame():callParserFunction('#time', 'H:i, j F Y', mw.getCurrentFrame():callParserFunction('REVISIONTIMESTAMP', titleOfPage.fullText))
			mainNotice:wikitext(' ' .. message('lastEdited', {
				'[[User:' .. lastEditUser .. '|' .. lastEditUser .. ']]',
				'<span class="plainlinks">[{{fullurl:' .. titleOfPage.fullText .. '|action=history}} ' .. editDate .. ']</span>',
				timeAgo.main({editDate})
			}))
			mainNotice:wikitext('\n')
			mainNotice:allDone()
			out = out .. tostring(mainNotice)
			-- categorize
			out = out .. '[[' .. (args.nocat and ':' or '') .. 'Category:' .. message('defaultCategory') .. ']]'
			for k,v in pairs(args.entries) do
				local categorizeTime = mw.getCurrentFrame():preprocess('{{#time:U|' .. args.date .. ' +' .. (v and v.delayCategorization or 0) .. ' days' .. '}}')
				local currentTime = mw.getCurrentFrame():preprocess('{{#time:U|now}}')
				if currentTime + 0 >= categorizeTime + 0 then
					for l,w in pairs(v and v.categories or {}) do
						out = out .. '[[' .. (args.nocat and ':' or '') .. 'Category:' .. w .. ']]'
					end
				end
			end
			local deletionBoxArgs = {
				type = "speedy",
				text = mw.getCurrentFrame():preprocess(out),
				style = "font-size:95%;word-break:break-word;",
				image = message('level' .. args.highestMessage .. 'icon') == '' and 'none' or '[[' .. message('level' .. args.highestMessage .. 'icon') .. '|40px]]'
			}
			local deletionBox = mbox.main('mbox', deletionBoxArgs)
			local blankedBox = ''
			local hiddenBox = ''
			local blanked = ''
			local hidden = ''
			if args.blank then
				blanked = blanked .. message('blanked')
				if mw.getCurrentFrame():preprocess('{{REVISIONSIZE}}') + 0 >= 35 then
					blanked = blanked .. ' <b>' .. message('pleaseBlank') .. '</b>'
				end
				local blankedBoxArgs = {
					type = "notice",
					text = blanked,
					style = "word-break:break-word;"
				}
				blankedBox = mbox.main('mbox', blankedBoxArgs)
			end
			if args.hide then
				hidden = hidden .. message('hidden')
				local hiddenBoxArgs = {
					type = "notice",
					text = hidden,
					style = "word-break:break-word;"
				}
				hiddenBox = mbox.main('mbox', hiddenBoxArgs)
			end
			if args.hide and not args.nocat then
				hiddenBox = hiddenBox .. '<div style="display:none">'
			end
			return deletionBox .. blankedBox .. hiddenBox
		end
	end
end

p.makeTable = makeInvokeFunc('_makeTable')
function p._makeTable(args)
	local usedCodes = {}
	local tb = mw.html.create("table")
	tb:addClass('wikitable')
	local th = tb:tag('tr')
	th:tag('th'):wikitext('Code'):done()
	th:tag('th'):wikitext('Aliases'):done()
	th:tag('th'):wikitext('Criterion'):done()
	th:tag('th'):wikitext('Name'):done()
	th:tag('th'):wikitext('Description'):done()
	for k,v in pairs(config.deletionReasonsSorting) do
		local entry = getDeletionEntry(v)
		if entry then
			if not usedCodes[v] then
				local tr = tb:tag('tr')
				tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('<code><nowiki>' .. v .. '</nowiki></code>')):done()
				local aliasStr = ''
				for _,alias in pairs(entry.aliases) do
					aliasStr = aliasStr .. '<code><nowiki>' .. alias .. '</nowiki></code>'
					usedCodes[alias] = true
				end
				tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess(aliasStr))
				tr:tag('td'):wikitext('[[Project:CSD#' .. entry.code .. '|' .. entry.code .. ']]'):done()
				tr:tag('td'):wikitext(entry.name):done()
				tr:tag('td'):wikitext(entry.description):done()
				usedCodes[v] = true
			end
		end
	end
	for k,entry in pairs(config.deletionCodes) do
		if not usedCodes[k] then
			local tr = tb:tag('tr')
			tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('<code><nowiki>' .. k .. '</nowiki></code>')):done()
			local aliasStr = ''
			for _,alias in pairs(entry.aliases) do
				aliasStr = aliasStr .. '<code><nowiki>' .. alias .. '</nowiki></code>'
				usedCodes[alias] = true
			end
			tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess(aliasStr))
			tr:tag('td'):wikitext('[[Project:CSD#' .. entry.code .. '|' .. entry.code .. ']]'):done()
			tr:tag('td'):wikitext(entry.name):done()
			tr:tag('td'):wikitext(entry.description):done()
			usedCodes[k] = true
		end
	end
	return tostring(tb) .. ''
end

p.makeTableWithExamples = makeInvokeFunc('_makeTableWithExamples')
function p._makeTableWithExamples(args)
	local usedCodes = {}
	local tb = mw.html.create("table")
	tb:addClass('wikitable')
	local th = tb:tag('tr')
	th:tag('th'):css{position = "sticky", top = 0, left = 0}:wikitext('Codes'):done()
	--th:tag('th'):wikitext('Criterion'):done()
	th:tag('th'):css{position = "sticky", top = 0}:wikitext('Deletion message'):done()
	th:tag('th'):css{position = "sticky", top = 0}:wikitext('Deletion notice'):done()
	for k,v in pairs(config.deletionReasonsSorting) do
		local entry = getDeletionEntry(v)
		if entry then
			if not usedCodes[v] then
				local tr = tb:tag('tr')
				local aliasStr = ''
				for _,alias in pairs(entry.aliases) do
					aliasStr = aliasStr .. '<br/><code><nowiki>{{db|' .. alias .. '}}</nowiki></code>'
					usedCodes[alias] = true
				end
				tr:tag('td'):css{position = "sticky", left = 0}:wikitext(mw.getCurrentFrame():preprocess('<code><nowiki>{{db|' .. v .. '}}</nowiki></code>' .. aliasStr)):done()
				--tr:tag('td'):wikitext('[[Project:CSD#' .. entry.code .. '|' .. entry.code .. ']]'):done()
				tr:tag('td'):wikitext('<div style="width:700px;">' .. mw.getCurrentFrame():preprocess('{{#invoke:Speedy|main|nocat=yes|' .. v .. '}}' .. '</div>' )):done()
				tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('<div style="width:700px;">' .. '{{#invoke:Speedy|main|page=Sandbox|notice=yes|' .. v .. '}}' .. '</div>')):done()
				usedCodes[v] = true
			end
		end
	end
	for v,entry in pairs(config.deletionCodes) do
		if not usedCodes[v] then
			local tr = tb:tag('tr')
			local aliasStr = ''
			for _,alias in pairs(entry.aliases) do
				aliasStr = aliasStr .. '<br/><code><nowiki>{{db|' .. alias .. '}}</nowiki></code>'
				usedCodes[alias] = true
			end
			tr:tag('td'):css{position = "sticky", left = 0}:wikitext(mw.getCurrentFrame():preprocess('<code><nowiki>{{db|' .. v .. '}}</nowiki></code>\n' .. aliasStr)):done()
			--tr:tag('td'):wikitext('[[Project:CSD#' .. entry.code .. '|' .. entry.code .. ']]'):done()
				tr:tag('td'):wikitext('<div style="width:700px;">' .. mw.getCurrentFrame():preprocess('{{#invoke:Speedy|main|nocat=yes|' .. v .. '}}' .. '</div>' )):done()
				tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('<div style="width:700px;">' .. '{{#invoke:Speedy|main|page=Sandbox|notice=yes|' .. v .. '}}' .. '</div>')):done()
			usedCodes[v] = true
		end
	end
	return tostring(tb) .. ''
end

return p