Module:UserLinks
--------------------------------------------------------------------------------
 --                                 UserLinks                                  --
 -- This module creates a list of links about a given user. It can be used on  --
 -- its own or from a template. See the /doc page for more documentation.      --
 --------------------------------------------------------------------------------
 
 -- Require necessary modules
 local yesno = require('Module:Yesno')
 
 -- Lazily initialise modules that we might or might not need
 local mExtra -- [[Module:UserLinks/extra]]
 local mArguments -- [[Module:Arguments]]
 local mToolbar -- [[Module:Toolbar]]
 local mCategoryHandler -- [[Module:Category handler]]
 local mTableTools -- [[Module:TableTools]]
 local interwikiTable -- [[Module:InterwikiTable]], loaded with mw.loadData
 
 -- Load shared helper functions
 local mShared = require('Module:UserLinks/shared')
 local raiseError = mShared.raiseError
 local maybeLoadModule = mShared.maybeLoadModule
 local makeWikitextError = mShared.makeWikitextError
 local makeWikilink = mShared.makeWikilink
 local makeUrlLink = mShared.makeUrlLink
 local makeFullUrlLink = mShared.makeFullUrlLink
 local message = mShared.message
 
 local p = {}
 
 --------------------------------------------------------------------------------
 -- Link table
 --------------------------------------------------------------------------------
 
 function p.getLinks(snippets)
 	--[=[
 	-- Get a table of links that can be indexed with link codes. The table
 	-- returned is blank, but links are added to it on demand when it is
 	-- indexed. This is made possible by the metatable and by the various link
 	-- functions, some of which are defined here, and some of which are defined
 	-- at [[Module:UserLinks/extra]].
 	--]=]
 	local links, linkFunctions = {}, {}
 
 	----------------------------------------------------------------------------
 	-- Link functions
 	--
 	-- The following functions make the links from the link codes and the user
 	-- data snippets. New link functions should be added below the existing
 	-- functions.
 	----------------------------------------------------------------------------
 
 	function linkFunctions.u(snippets)
 		-- User page
 		return makeWikilink(
 			snippets.interwiki,
 			2,
 			snippets.username,
 			snippets.username
 		)
 	end
 	
 	function linkFunctions.np(snippets)
 		-- User page (no ping)
 		return '&lt;span class=&quot;plainlinks&quot;&gt;' .. makeFullUrlLink(
 			snippets.interwiki,
 			2,
 			snippets.username,
 			'',
 			snippets.username
 		) .. '&lt;/span&gt;'
 	end
 
 	function linkFunctions.t(snippets)
 		-- User talk page
 		return makeWikilink(
 			snippets.interwiki,
 			3,
 			snippets.username,
 			message('display-talk')
 		)
 	end
 
 	function linkFunctions.c(snippets)
 		-- Contributions
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Contribs/' .. snippets.username,
 			message('display-contributions')
 		)
 	end
 	
 	function linkFunctions.c64(snippets)
 		-- Contributions
 		local first64 = snippets.username:match('^%x+:%x+:%x+:%x+:')
 			or snippets.username:match('^%x+:%x+:%x+:')
 			or snippets.username:match('^%x+:%x+:')
 			or snippets.username:match('^%x+:')
 		return first64 and makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Contribs/' .. first64 .. ':/64',
 			'(/64)'
 		) or ''
 	end
 
 	function linkFunctions.ct(snippets)
 		-- Edit count
 		return makeUrlLink(
 			{
 				host = 'xtools.wmflabs.org',
 				path = '/ec/',
 				query = {
 					username = snippets.username,
 					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
 				}
 			},
 			message('display-count')
 		)
 	end
 
 	function linkFunctions.m(snippets)
 		-- Page moves
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Log/move/' .. snippets.username,
 			message('display-moves')
 		)
 	end
 
 	function linkFunctions.l(snippets)
 		-- Logs
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Log/' .. snippets.username,
 			message('display-logs')
 		)
 	end
 
 	function linkFunctions.ae(snippets)
 		-- Automated edits (and non-automated contributions).
 		return makeUrlLink(
 			{
 				host = 'xtools.wmflabs.org',
 				path = '/autoedits/',
 				query = {
 					username = snippets.username,
 					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
 				}
 			},
 			message('display-autoedits')
 		)
 	end
 
 	function linkFunctions.bl(snippets)
 		-- Block log
 		return makeFullUrlLink(
 			snippets.interwiki,
 			-1,
 			'Log/block',
 			{page = 'User:' .. snippets.username},
 			message('display-blocklog')
 		)
 	end
 
 	function linkFunctions.bls(snippets)
 		-- Blocks
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Log/block/' .. snippets.username,
 			message('display-blocks')
 		)
 	end
 
 	function linkFunctions.bu(snippets)
 		-- Block user
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Block/' .. snippets.username,
 			message('display-blockuser')
 		)
 	end
 
 	function linkFunctions.ca(snippets)
 		-- Central auth
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'CentralAuth/' .. snippets.username,
 			message('display-centralauth')
 		)
 	end
 
 	function linkFunctions.dc(snippets)
 		-- Deleted contribs
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'DeletedContributions/' .. snippets.username,
 			message('display-deletedcontributions')
 		)
 	end
 
 	function linkFunctions.e(snippets)
 		-- Email
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'EmailUser/' .. snippets.username,
 			message('display-email')
 		)
 	end
 
 	function linkFunctions.es(snippets)
 		-- Edit summaries
 		return makeUrlLink(
 			{
 				host = 'xtools.wmflabs.org',
 				path = '/editsummary/',
 				query = {
 					username = snippets.username,
 					project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
 				}
 			},
 			message('display-editsummaries')
 		)
 	end
 
 	function linkFunctions.del(snippets)
 		-- Deletions
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Log/delete/' .. snippets.username,
 			message('display-deletions')
 		)
 	end
 
 	function linkFunctions.lu(snippets)
 		-- List user
 		return makeFullUrlLink(
 			snippets.interwiki,
 			-1,
 			'ListUsers',
 			{limit = 1, username = snippets.username},
 			message('display-listuser')
 		)
 	end
 
 	function linkFunctions.sul(snippets)
 		-- SUL
 		return makeWikilink(
 			nil,
 			nil,
 			'sulutil:' .. snippets.username,
 			message('display-sul')
 		)
 	end
 
 	function linkFunctions.tl(snippets)
 		-- Target logs
 		return makeFullUrlLink(
 			snippets.interwiki,
 			-1,
 			'Log',
 			{page = mw.site.namespaces[2].name .. ':' .. snippets.username},
 			message('display-targetlogs')
 		)
 	end
 
 	function linkFunctions.efl(snippets)
 		-- Edit filter log
 		return makeFullUrlLink(
 			snippets.interwiki,
 			-1,
 			'AbuseLog',
 			{wpSearchUser = snippets.username},
 			message('display-abuselog')
 		)
 	end
 
 	function linkFunctions.pr(snippets)
 		-- Protections
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Log/protect/' .. snippets.username,
 			message('display-protections')
 		)
 	end
 
 	function linkFunctions.rl(snippets)
 		-- User rights
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Log/rights/' .. snippets.username,
 			message('display-rights')
 		)
 	end
 
 	function linkFunctions.ren(snippets)
 		-- Renames
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Log/renameuser/' .. snippets.username,
 			message('display-renames')
 		)
 	end
 
 	function linkFunctions.rfa(snippets)
 		-- Requests for adminship
 		return makeWikilink(
 			nil,
 			-1,
 			'PrefixIndex/' .. message('page-rfa') .. '/' .. snippets.username,
 			message('display-rfa')
 		)
 	end
 
 	function linkFunctions.api(snippets)
 		-- API user data
 		return makeUrlLink(
 			{
 				host = snippets.fullDomain,
 				path = '/w/api.php',
 				query = {
 					action = 'query',
 					list = 'users',
 					usprop = 'groups|editcount',
 					ususers = snippets.username
 				}
 			},
 			message('display-api')
 		)
 	end
 
 	function linkFunctions.up(snippets)
 		-- Uploads
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'ListFiles/' .. snippets.username,
 			message('display-uploads')
 		)
 	end
 	
 	function linkFunctions.nuke(snippets)
 		-- Mass delete/Special:Nuke
 		return makeWikilink(
 			snippets.interwiki,
 			-1,
 			'Nuke/' .. snippets.username,
 			message('display-nuke')
 
 		)
 	end
 	
 	----------------------------------------------------------------------------
 	-- End of link functions
 	----------------------------------------------------------------------------
 
 	-- Define the metatable that memoizes the link functions, and fetches link
 	-- functions from [[Module:UserLinks/extra]] if necessary.
 
 	-- Lazily initialise the extraLinkFunctions table. We only want to load
 	-- [[Module:UserLinks/extra]] as necessary, so it has a low transclusion
 	-- count.
 	local extraLinkFunctions
 
 	-- Define functions for shared code in the metatable.
 	local function validateCode(code)
 		-- Checks whether code is a valid link code - i.e. checks that it is a
 		-- string and that it is not the blank string. Returns the code if
 		-- the check passes, and nil if not.
 		if type(code) == 'string' and code ~= '' then
 			return code
 		else
 			return nil
 		end
 	end
 
 	local function getExtraLinkFunctions()
 		-- Loads the table of extra link functions from the /extra module.
 		-- If there is a problem with loading it, return false. We use the
 		-- distinction between false and nil to record whether we have already
 		-- tried to load it.
 		if extraLinkFunctions ~= nil then
 			return extraLinkFunctions
 		end
 		if mExtra == nil then
 			-- If loading the module fails, maybeLoadModule returns false.
 			-- Here we use the distinction between false and nil to record
 			-- whether we have already tried to load the /extra module.
 			mExtra = maybeLoadModule('Module:UserLinks/extra')
 		end
 		if type(mExtra) == 'table'
 			and type(mExtra.linkFunctions) == 'table'
 		then
 			extraLinkFunctions = mExtra.linkFunctions
 		else
 			extraLinkFunctions = false
 		end
 		return extraLinkFunctions
 	end
 
 	local function memoizeExtraLink(code, func)
 		local success, link = pcall(func, snippets)
 		if success and type(link) == 'string' then
 			links[code] = link
 			return link
 		end
 		return nil
 	end
 
 	-- Define the metatable.
 	setmetatable(links, {
 		__index = function (t, key)
 			local code = validateCode(key)
 			if not code then
 				raiseError(
 					message('error-malformedlinkcode'),
 					message('error-malformedlinkcode-section')
 				)
 			end
 			local linkFunction = linkFunctions[code]
 			local link
 			if linkFunction then
 				link = linkFunction(snippets)
 				links[code] = link
 			else
 				extraLinkFunctions = getExtraLinkFunctions()
 				if extraLinkFunctions then
 					local extraLinkFunction = extraLinkFunctions[code]
 					if type(extraLinkFunction) == 'function' then
 						link = memoizeExtraLink(code, extraLinkFunction)
 					end
 				end
 			end
 			if link then
 				return link
 			else
 				raiseError(
 					message('error-invalidlinkcode', code),
 					message('error-invalidlinkcode-section')
 				)
 			end
 		end,
 		__pairs = function ()
 			extraLinkFunctions = getExtraLinkFunctions()
 			if extraLinkFunctions then
 				for code, func in pairs(extraLinkFunctions) do
 					if validateCode(code) and type(func) == 'function' then
 						memoizeExtraLink(code, func)
 					end
 				end
 			end
 			-- Allow built-in functions to overwrite extra functions.
 			for code, func in pairs(linkFunctions) do
 				local link = func(snippets)
 				links[code] = link
 			end
 			return function (t, key)
 				return next(links, key)
 			end
 		end
 	})
 	return links
 end
 
 --------------------------------------------------------------------------------
 -- User data snippets
 --------------------------------------------------------------------------------
 
 function p.getSnippets(args)
 	--[=[
 	-- This function gets user data snippets from the arguments, and from
 	-- [[Module:InterwikiTable]]. The data is loaded as necessary and memoized
 	-- in the snippets table for performance. 
 	--
 	-- Snippets default to the blank string, '', so they can be used in
 	-- concatenation operations without coders having to worry about raising
 	-- errors. Because of this, the local functions snippetExists and
 	-- getSnippet have been written to aid people writing new snippets. These
 	-- functions treat the blank string as false. It is not necessary to return
 	-- the blank string from a snippet function, as nil and false values are
 	-- automatically converted into the blank string by the metatable.
 	--
 	-- If you add a new snippet, please document it at
 	-- [[Module:UserLinks#Adding new links]].
 	--]=]
 	local snippets, snippetFunctions = {}, {}
 	setmetatable(snippets, {
 		__index = function (t, key)
 			local snippetFunction = snippetFunctions[key]
 			if snippetFunction then
 				snippets[key] = snippetFunction() or ''
 				return snippets[key]
 			else
 				raiseError(
 					message('error-nosnippet', key),
 					message('error-nosnippet-section')
 				)
 			end
 		end
 	})
 
 	-- Define helper functions for writting the snippet functions.
 	local function snippetExists(key)
 		-- We have set the metatable up to make snippets default to '', so we
 		-- don't have to test for false or nil.
 		return snippets[key] ~= ''
 	end
 
 	local function getSnippet(key)
 		local ret = snippets[key]
 		if ret == '' then
 			return nil
 		else
 			return ret
 		end
 	end
 
 	-- Start snippet functions.
 
 	function snippetFunctions.username()
 		-- The username.
 		local username = args.user or args.User
 		return username or raiseError(
 			message('error-nousername'),
 			message('error-nousername-section')
 		)
 	end
 
 	function snippetFunctions.usernameHtml()
 		-- The username html-encoded. Spaces are encoded as pluses.
 		return mw.uri.encode(snippets.username)
 	end
 
 	function snippetFunctions.project()
 		-- The project name.
 		-- Also does the work for snippetFunctions.interwikiTableKey, and adds
 		-- the project value to snippets.lang if it is a valid language code.
 		local project = args.Project or args.project
 		if not project then
 			return nil
 		end
 		local projectValidated, interwikiTableKey = p.validateProjectCode(project)
 		if not projectValidated then
 			if mw.language.isKnownLanguageTag(project) then
 				if not snippetExists('lang') then
 					snippets.lang = project
 				end
 			else
 				raiseError(
 					message('error-invalidproject', project),
 					message('error-invalidproject-section')
 				)
 			end
 		end
 		snippets.interwikiTableKey = interwikiTableKey
 		return project
 	end
 
 	function snippetFunctions.interwikiTableKey()
 		-- The key for the project in Module:InterwikiTable.
 		-- Relies on snippetFunctions.project to do the real work.
 		local temp = snippets.project -- required; puts key in snippets table
 		return rawget(snippets, 'interwikiTableKey')
 	end
 
 	function snippetFunctions.toolProject()
 		-- The short project code for use with toolserver or labs. It is always
 		-- present, even if the &quot;project&quot; argument is absent. The default value
 		-- is the &quot;snippet-project-default&quot; message.
 		local project = getSnippet('project')
 		if not project then
 			return message('snippet-project-default')
 		else
 			return project
 		end
 	end
 
 	function snippetFunctions.projectLong()
 		-- The long form of the project name, e.g. &quot;wikipedia&quot; or &quot;wikibooks&quot;.
 		local key = getSnippet('interwikiTableKey')
 		if not key then
 			return message('snippet-projectlong-default')
 		end
 		interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
 		local prefixes = interwikiTable[key].iw_prefix
 		-- Using prefixes[2] is a bit of a hack, but should find the long name
 		-- most of the time.
 		return prefixes[2] or prefixes[1] 
 	end
 
 	function snippetFunctions.lang()
 		-- The language code.
 		local lang = args.lang or args.Lang
 		if not lang then
 			return nil
 		end
 		if mw.language.isKnownLanguageTag(lang) then
 			return lang
 		else
 			raiseError(
 				message('error-invalidlanguage', lang),
 				message('error-invalidlanguage-section')
 			)
 		end
 	end
 
 	function snippetFunctions.toolLang()
 		-- The language code for use with toolserver or labs tools. It is always
 		-- present, even if the &quot;lang&quot; argument is absent. The default value is
 		-- the &quot;snippet-lang-default&quot; message. 
 		return getSnippet('lang') or message('snippet-lang-default')
 	end
 
 	function snippetFunctions.interwiki()
 		-- The interwiki prefix, consisting of the project and language values,
 		-- separated by colons, e.g. &quot;:wikt:es:&quot;.
 		local project = getSnippet('project')
 		local lang = getSnippet('lang')
 		if not project and not lang then
 			return nil
 		end
 		local ret = {}
 		ret[#ret + 1] = project
 		ret[#ret + 1] = lang
 		return table.concat(ret, ':')
 	end
 
 	function snippetFunctions.fullDomain()
 		-- The full domain name of the site, e.g. www.mediawiki.org,
 		-- en.wikpedia.org, or ja.wikibooks.org.
 		local fullDomain
 		local lang = getSnippet('toolLang')
 		local key = getSnippet('interwikiTableKey')
 		if key then
 			interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
 			local domain = interwikiTable[key].domain
 			local takesLangPrefix = interwikiTable[key].takes_lang_prefix
 			if takesLangPrefix then
 				fullDomain = lang .. '.' .. domain
 			else
 				fullDomain = domain
 			end
 		else
 			fullDomain = lang .. '.wikipedia.org'
 		end
 		return fullDomain
 	end
 
 	-- End snippet functions. If you add a new snippet function, please
 	-- document it at [[Module:UserLinks#Adding new links]].
 
 	return snippets
 end 
 
 function p.validateProjectCode(s)
 	-- Validates a project code, by seeing whether it is present in
 	-- [[Module:InterwikiTable]]. If it is present, returns the code and the
 	-- InterwikiTable key for the corresponding site. If not present,
 	-- returns nil for both.
 	interwikiTable = interwikiTable or mw.loadData('Module:InterwikiTable')
 	for key, t in pairs(interwikiTable) do
 		for i, prefix in ipairs(t.iw_prefix) do
 			if s == prefix then
 				return s, key
 			end
 		end
 	end
 	return nil, nil
 end
 
 --------------------------------------------------------------------------------
 -- Main functions
 --------------------------------------------------------------------------------
 
 local function makeInvokeFunction(funcName)
 	-- Makes a function that can be accessed from #invoke. This is only required
 	-- for functions that need to access arguments.
 	return function (frame)
 		mArguments = require('Module:Arguments')
 		local args = mArguments.getArgs(frame)
 		return p[funcName](args)
 	end
 end
 
 p.main = makeInvokeFunction('_main')
 
 function p._main(args)
 	-- The main function. This is the one called from [[Template:User-multi]],
 	-- via p.main.
 	local options = p.getOptions(args)
 	local snippets = p.getSnippets(args)
 	local codes = p.getCodes(args)
 	local links = p.getLinks(snippets)
 	-- Overload the built-in Lua error function to generate wikitext errors
 	-- meant for end users to see. This makes things harder to debug when
 	-- real errors occur, but it is the only realistic way to show wikitext
 	-- errors and and still have sane code when using metatables, etc.
 	local success, result = pcall(p.export, codes, links, options)
 	if success then
 		return result
 	else
 		return makeWikitextError(result, options.isDemo)
 	end
 end
 
 function p.getOptions(args)
 	-- Gets the options from the args table, so that we don't have to pass
 	-- around the whole args table all the time.
 	local options = {}
 	options.isDemo = yesno(args.demo) or false
 	options.noPing = yesno(args.noPing) or yesno(args.noping) or yesno(args.np) or false
 	options.toolbarStyle = yesno(args.small) and 'font-size: 90%;' or nil
 	options.sup = yesno(args.sup, true)
 	options.separator = args.separator
 	options.span = args.span
 	return options
 end
 
 function p.getCodes(args)
 	-- Gets the link codes from the arguments. The codes aren't validated
 	-- at this point.
 	mTableTools = maybeLoadModule('Module:TableTools')
 	local codes
 	if mTableTools then
 		codes = mTableTools.compressSparseArray(args)
 	else
 		codes = {}
 		for i, code in ipairs(args) do
 			codes[i] = code
 		end
 	end
 	return codes
 end
 
 function p.export(codes, links, options)
 	-- Make the user link.
 	local userLink = options.noPing and links.np or links.u
 
 	-- If we weren't passed any link codes, just return the user link.
 	if #codes &lt; 1 then
 		return userLink
 	end
 
 	-- Make the toolbar.
 	mToolbar = require('Module:Toolbar')
 	local toolbarArgs = {}
 	for i, code in ipairs(codes) do
 		local link = links[code]
 		toolbarArgs[#toolbarArgs + 1] = link
 	end
 	toolbarArgs.style = options.toolbarStyle
 	toolbarArgs.separator = options.separator or 'dot'
 	toolbarArgs.span = options.span
 	local toolbar = mToolbar.main(toolbarArgs)
 
 	-- Apply the sup option.
 	if options.sup then
 		toolbar = '&lt;sup&gt;' .. toolbar .. '&lt;/sup&gt;'
 	end
 	
 	-- If we are transcluding, add a non-breaking space, but if we are substing
 	-- just use a normal space
 	local space = mw.isSubsting() and ' ' or '&amp;nbsp;'
 	
 	return userLink .. space .. toolbar
 end
 
 --------------------------------------------------------------------------------
 -- Single link function
 --------------------------------------------------------------------------------
 
 p.single = makeInvokeFunction('_single')
 
 function p._single(args)
 	-- Fetches a single link from the link table.
 	local options = p.getOptions(args)
 	local snippets = p.getSnippets(args)
 	local links = p.getLinks(snippets)
 	local code = args[1]
 	local success, link = pcall(p.exportSingle, links, code)
 	if success then
 		return link
 	else
 		return makeWikitextError(link, options.isDemo)
 	end
 end
 
 function p.exportSingle(links, code)
 	-- If any errors occur, they will probably occur here. This function
 	-- exists purely so that all the errors that will occur in p._single can
 	-- be handled using a single pcall.
 	if not code then
 		raiseError(
 			message('error-nolinkcode'),
 			message('error-nolinkcode-section')
 		)
 	end
 	return links[code]
 end
 
 --------------------------------------------------------------------------------
 -- Link table
 --------------------------------------------------------------------------------
 
 function p.linktable()
 	-- Returns a wikitext table of link codes, with an example link for each
 	-- one. This function doesn't take any arguments, so it can be accessed
 	-- directly from wiki pages without using makeInvokeFunction.
 	local args = {user = 'Example'}
 	local snippets = p.getSnippets(args)
 	local links = p.getLinks(snippets)
 
 	-- Assemble the codes and links in order
 	local firstCodes = {'u', 't', 'c'}
 	local firstLinks, firstCodesKeys = {}, {}
 	for i, code in ipairs(firstCodes) do
 		firstCodesKeys[code] = true
 		firstLinks[#firstLinks + 1] = {code, links[code]}
 	end
 	local secondLinks = {}
 	for code, link in pairs(links) do
 		if not firstCodesKeys[code] then
 			secondLinks[#secondLinks + 1] = {code, link}
 		end
 	end
 	table.sort(secondLinks, function(t1, t2)
 		return t1[1] &lt; t2[1]
 	end)
 	local links = {}
 	for i, t in ipairs(firstLinks) do
 		links[#links + 1] = t
 	end
 	for i, t in ipairs(secondLinks) do
 		links[#links + 1] = t
 	end
 
 	-- Output the code table in table format
 	local ret = {}
 	ret[#ret + 1] = '{| class=&quot;wikitable plainlinks sortable&quot;'
 	ret[#ret + 1] = '|-'
 	ret[#ret + 1] = '! ' .. message('linktable-codeheader')
 	ret[#ret + 1] = '! ' .. message('linktable-previewheader')
 	for i, t in ipairs(links) do
 		local code = t[1]
 		local link = t[2]
 		ret[#ret + 1] = '|-'
 		ret[#ret + 1] = &quot;| '''&quot; .. code .. &quot;'''&quot; 
 		ret[#ret + 1] = '| ' .. link
 	end
 	ret[#ret + 1] = '|}'
 	return table.concat(ret, '\n')
 end