Module:UserLinks: Difference between revisions

From the Croc Wiki, the Croc encyclopedia
Jump to navigationJump to search
Content added Content deleted
(update count link per protected edit request by User:Frietjes)
m (45 revisions imported from wikipedia:Module:UserLinks)
 
(16 intermediate revisions by 7 users not shown)
Line 1: Line 1:
--------------------------------------------------------------------------------
local ToolbarBuilder = require('Module:Toolbar')
-- UserLinks --
local interwikiTable = mw.loadData("Module:InterwikiTable")
-- 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 u = {} -- Table for user-data helper strings.
local yesno = require('Module:Yesno')
local trackingCategories = {} -- Table for storing the tracking categories.
local demo


-- Lazily initialise modules that we might or might not need
-- Define a custom error message for this module.
local mExtra -- [[Module:UserLinks/extra]]
local function err(msg, section)
local mArguments -- [[Module:Arguments]]
local help
local mToolbar -- [[Module:Toolbar]]
if section then
local mCategoryHandler -- [[Module:Category handler]]
help = ' ([[Template:User-multi#' .. section .. '|help]])'
local mTableTools -- [[Module:TableTools]]
else
local interwikiTable -- [[Module:InterwikiTable]], loaded with mw.loadData
help = ''
end
local cat
if demo == 'yes' then
cat = ''
else
cat = '[[Category:UserLinks transclusions with errors]]'
end
return '<span class="error">[[Template:User-multi|User-multi]] error: ' .. msg
.. help .. '.</span>' .. cat
end


-- Load shared helper functions
----------------------------------------------------------------------------------------------
local mShared = require('Module:UserLinks/shared')
-- To add more link types, write a function that produces an individual link, and put --
local raiseError = mShared.raiseError
-- it at the bottom of the list below. Then, add a link code for your function to the --
local maybeLoadModule = mShared.maybeLoadModule
-- "linktypes" table. Try and make the code three letters or less. There are a number --
local makeWikitextError = mShared.makeWikitextError
-- of helper strings available for writing the functions: --
local makeWikilink = mShared.makeWikilink
-- --
local makeUrlLink = mShared.makeUrlLink
-- u.username The plain username. If the username is not present then the --
local makeFullUrlLink = mShared.makeFullUrlLink
-- module returns an error. --
local message = mShared.message
-- u.usernameHtml The username html-encoded. Spaces are encoded with plus signs. --
-- u.project The project name. Nil if not specified. --
-- u.lang The language code. Nil if not specified. --
-- u.interwiki The interwiki prefix, consisting of the project and language --
-- values, separated by colons, e.g. ":wikt:es:". If no project --
-- or language values are found, this is the blank string, "". --
-- u.projectCode If a valid project is specified, this is the code for that --
-- project in [[Module:InterwikiTable]]. Otherwise this is nil. --
-- u.projectLong The long project name, e.g. "wikipedia" or "wikibooks". If --
-- not specified the default is "wikipedia". --
-- u.toolLang The language code for use with toolserver or labs tools. The --
-- default is "en". --
-- --
-- If you want more helper strings, you can define them in the generateUserDataStrings --
-- function below. --
----------------------------------------------------------------------------------------------


local p = {}
----------------------------------------------------------------------------------------------
-- LINK FUNCTIONS START --
----------------------------------------------------------------------------------------------


--------------------------------------------------------------------------------
local function makeUserLink()
-- Link table
return '[[' .. u.interwiki .. 'User:' .. u.username .. '|' .. u.username .. ']]'
--------------------------------------------------------------------------------
end


local function makeTalkLink()
function p.getLinks(snippets)
--[=[
return '[[' .. u.interwiki .. 'User talk:' .. u.username .. '|talk]]'
-- Get a table of links that can be indexed with link codes. The table
end
-- 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 = {}, {}


----------------------------------------------------------------------------
local function makeContribsLink()
-- Link functions
return '[[' .. u.interwiki .. 'Special:Contributions/' .. u.username .. '|contribs]]'
--
end
-- 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.
----------------------------------------------------------------------------


local function makeCountLink()
function linkFunctions.u(snippets)
-- User page
return '[//tools.wmflabs.org/supercount/index.php?user=' .. u.usernameHtml
return makeWikilink(
.. '&project=' .. u.toolLang .. '.' .. u.projectLong
snippets.interwiki,
.. ' count]'
2,
end
snippets.username,
snippets.username
)
end


local function makeMovesLink()
function linkFunctions.t(snippets)
-- User talk page
return '[[' .. u.interwiki .. 'Special:Log/move/' .. u.username .. '|page&nbsp;moves]]'
return makeWikilink(
end
snippets.interwiki,
3,
snippets.username,
message('display-talk')
)
end


local function makeLogsLink()
function linkFunctions.c(snippets)
-- Contributions
return '[[' .. u.interwiki .. 'Special:Log/' .. u.username .. '|logs]]'
return makeWikilink(
end
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


local function makeBlockLogLink()
function linkFunctions.ct(snippets)
-- Edit count
local url = mw.uri.fullUrl(u.interwiki .. 'Special:Log/block', 'page=User:' .. u.usernameHtml)
return makeUrlLink(
return '[' .. tostring(url) .. ' block&nbsp;log]'
{
end
host = 'xtools.wmflabs.org',
path = '/ec/',
query = {
username = snippets.username,
project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
}
},
message('display-count')
)
end


local function makeBlocksLink()
function linkFunctions.m(snippets)
-- Page moves
return '[[' .. u.interwiki .. 'Special:Log/block/' .. u.username .. '|blocks]]'
return makeWikilink(
end
snippets.interwiki,
-1,
'Log/move/' .. snippets.username,
message('display-moves')
)
end


local function makeBlockUserLink()
function linkFunctions.l(snippets)
-- Logs
return '[[' .. u.interwiki .. 'Special:Block/' .. u.username .. '|block&nbsp;user]]'
return makeWikilink(
end
snippets.interwiki,
-1,
'Log/' .. snippets.username,
message('display-logs')
)
end


local function makeCentralAuthLink()
function linkFunctions.ae(snippets)
-- Automated edits (and non-automated contributions).
return '[[' .. u.interwiki .. 'Special:CentralAuth/' .. u.username .. '|central&nbsp;auth]]'
return makeUrlLink(
end
{
host = 'xtools.wmflabs.org',
path = '/autoedits/',
query = {
username = snippets.username,
project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
}
},
message('display-autoedits')
)
end


local function makeDeletedContribsLink()
function linkFunctions.bl(snippets)
-- Block log
return '[[' .. u.interwiki .. 'Special:DeletedContributions/' .. u.username .. '|deleted&nbsp;contribs]]'
return makeFullUrlLink(
end
snippets.interwiki,
-1,
'Log/block',
{page = 'User:' .. snippets.username},
message('display-blocklog')
)
end


local function makeEmailLink()
function linkFunctions.bls(snippets)
-- Blocks
return '[[' .. u.interwiki .. 'Special:Emailuser/' .. u.username .. '|email]]'
return makeWikilink(
end
snippets.interwiki,
-1,
'Log/block/' .. snippets.username,
message('display-blocks')
)
end


local function makeEditSummariesLink()
function linkFunctions.bu(snippets)
-- Block user
return '[http://tools.wmflabs.org/xtools/editsummary/index.php?name=' .. u.usernameHtml
return makeWikilink(
.. '&lang=' .. u.toolLang
snippets.interwiki,
.. '&wiki=' .. u.projectLong
-1,
.. ' edit&nbsp;summaries]'
'Block/' .. snippets.username,
end
message('display-blockuser')
)
end


local function makeDeletionsLink()
function linkFunctions.ca(snippets)
-- Central auth
return '[[' .. u.interwiki .. 'Special:Log/delete/' .. u.username .. '|deletions]]'
return makeWikilink(
end
snippets.interwiki,
-1,
'CentralAuth/' .. snippets.username,
message('display-centralauth')
)
end


local function makeListUserLink()
function linkFunctions.dc(snippets)
-- Deleted contribs
local url = mw.uri.fullUrl(u.interwiki .. 'Special:ListUsers', 'limit=1&username=' .. u.usernameHtml)
return makeWikilink(
return '[' .. tostring(url) .. ' list&nbsp;user]'
snippets.interwiki,
end
-1,
'DeletedContributions/' .. snippets.username,
message('display-deletedcontributions')
)
end


local function makeSulLink()
function linkFunctions.e(snippets)
-- Email
return '[[sulutil:' .. u.username .. '|global&nbsp;contribs]]'
return makeWikilink(
end
snippets.interwiki,
-1,
'EmailUser/' .. snippets.username,
message('display-email')
)
end


local function makeTargetLogsLink()
function linkFunctions.es(snippets)
-- Edit summaries
local url = mw.uri.fullUrl(u.interwiki .. 'Special:Log', 'page=User:' .. u.usernameHtml)
return makeUrlLink(
return '[' .. tostring(url) .. ' target&nbsp;logs]'
{
end
host = 'xtools.wmflabs.org',
path = '/editsummary/',
query = {
username = snippets.username,
project = snippets.toolLang .. '.' .. snippets.projectLong .. '.org'
}
},
message('display-editsummaries')
)
end


local function makeEditFilterLogLink()
function linkFunctions.del(snippets)
-- Deletions
local url = mw.uri.fullUrl(u.interwiki .. 'Special:AbuseLog', 'wpSearchUser=' .. u.usernameHtml)
return makeWikilink(
return '[' .. tostring(url) .. ' edit&nbsp;filter&nbsp;log]'
snippets.interwiki,
end
-1,
'Log/delete/' .. snippets.username,
message('display-deletions')
)
end


local function makeProtectionsLink()
function linkFunctions.lu(snippets)
-- List user
return '[[' .. u.interwiki .. 'Special:Log/protect/' .. u.username .. '|protections]]'
return makeFullUrlLink(
end
snippets.interwiki,
-1,
'ListUsers',
{limit = 1, username = snippets.username},
message('display-listuser')
)
end


local function makeRightsLink()
function linkFunctions.sul(snippets)
-- SUL
return '[[' .. u.interwiki .. 'Special:Log/rights/' .. u.username .. '|rights]]'
return makeWikilink(
end
nil,
nil,
'sulutil:' .. snippets.username,
message('display-sul')
)
end


local function makeRenamesLink()
function linkFunctions.tl(snippets)
-- Target logs
return '[[' .. u.interwiki .. 'Special:Log/renameuser/' .. u.username .. '|renames]]'
return makeFullUrlLink(
end
snippets.interwiki,
-1,
'Log',
{page = mw.site.namespaces[2].name .. ':' .. snippets.username},
message('display-targetlogs')
)
end


local function makeRfaLink()
function linkFunctions.efl(snippets)
-- Edit filter log
if u.project or u.lang then
return makeFullUrlLink(
table.insert( trackingCategories, '[[Category:UserLinks transclusions with unresolvable interwiki links]]' )
snippets.interwiki,
end
-1,
return '[[Special:PrefixIndex/Wikipedia:Requests for adminship/' .. u.username .. '|RfA]]'
'AbuseLog',
end
{wpSearchUser = snippets.username},
message('display-abuselog')
)
end


local function makeApiLink()
function linkFunctions.pr(snippets)
-- Protections
-- Find the full domain, as the API can't be accessed through the interwiki system.
return makeWikilink(
local fulldomain
snippets.interwiki,
local lang = u.lang or 'en'
-1,
if u.projectCode then
'Log/protect/' .. snippets.username,
local domain = interwikiTable[u.projectCode].domain
message('display-protections')
local takesLangPrefix = interwikiTable[u.projectCode].takes_lang_prefix
)
if not takesLangPrefix then
end
fulldomain = domain
else
fulldomain = lang .. '.' .. domain
end
else
fulldomain = lang .. '.wikipedia.org'
end
-- Return the API link
return '[//' .. fulldomain .. '/w/api.php?action=query&list=users&usprop=editcount&ususers=' .. u.usernameHtml .. ' api]'
end


local function makeUploadsLink()
function linkFunctions.rl(snippets)
-- User rights
return '[[' .. u.interwiki .. 'Special:ListFiles/' .. u.username .. '|uploads]]'
return makeWikilink(
end
snippets.interwiki,
-1,
'Log/rights/' .. snippets.username,
message('display-rights')
)
end


function linkFunctions.ren(snippets)
----------------------------------------------------------------------------------------------
-- Renames
-- LINK FUNCTIONS END --
return makeWikilink(
-- To enable new link functions, add the code to the "linktypes" table directly below. --
snippets.interwiki,
----------------------------------------------------------------------------------------------
-1,
'Log/renameuser/' .. snippets.username,
message('display-renames')
)
end


function linkFunctions.rfa(snippets)
local linktypes = {
-- Requests for adminship
{'t' , makeTalkLink},
return makeWikilink(
{'c' , makeContribsLink},
nil,
{'ct' , makeCountLink},
-1,
{'m' , makeMovesLink},
'PrefixIndex/' .. message('page-rfa') .. '/' .. snippets.username,
{'l' , makeLogsLink},
message('display-rfa')
{'bl' , makeBlockLogLink},
)
{'bls' , makeBlocksLink},
end
{'bu' , makeBlockUserLink},
{'ca' , makeCentralAuthLink},
{'dc' , makeDeletedContribsLink},
{'e' , makeEmailLink},
{'es' , makeEditSummariesLink},
{'del' , makeDeletionsLink},
{'lu' , makeListUserLink},
{'sul' , makeSulLink},
{'tl' , makeTargetLogsLink},
{'efl' , makeEditFilterLogLink},
{'pr' , makeProtectionsLink},
{'rl' , makeRightsLink},
{'ren' , makeRenamesLink},
{'rfa' , makeRfaLink},
{'api' , makeApiLink},
{'up' , makeUploadsLink}
}


local function getLink(linktype)
function linkFunctions.api(snippets)
-- API user data
local linkNumber
return makeUrlLink(
for i, value in ipairs(linktypes) do
{
if value[1] == linktype then
host = snippets.fullDomain,
linkNumber = i
path = '/w/api.php',
break
query = {
end
action = 'query',
end
list = 'users',
if not linkNumber then
usprop = 'groups|editcount',
return err('"' .. linktype .. '" is not a valid link code', 'Not a valid link code')
ususers = snippets.username
end
}
local result = linktypes[linkNumber][2]()
},
if type(result) ~= 'string' then
message('display-api')
return err(
)
'the function for code "' .. linktype .. '" did not return a string value',
end
'Function did not return a string value'
)
end
return result
end


local function makeToolbar(args)
function linkFunctions.up(snippets)
-- Uploads
local targs = {}
return makeWikilink(
local numArgsExist = false
snippets.interwiki,
for k, v in pairs(args) do
-1,
if type(k) == 'number' then
'ListFiles/' .. snippets.username,
numArgsExist = true
message('display-uploads')
targs[k] = getLink(v)
)
end
end
end
targs.style = args.small and 'font-size: 90%;'
----------------------------------------------------------------------------
targs.separator = args.separator or 'dot'
-- End of link functions
----------------------------------------------------------------------------
if numArgsExist == false then

return nil -- Don't return a toolbar if no numeric arguments exist.
-- Define the metatable that memoizes the link functions, and fetches link
else
-- functions from [[Module:UserLinks/extra]] if necessary.
return ToolbarBuilder.main(targs)

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


--------------------------------------------------------------------------------
-- This function finds whether a string is a valid interwiki project prefix.
-- User data snippets
-- If the string is valid, the function outputs two values: true, and the site code
--------------------------------------------------------------------------------
-- used in [[Module:InterwikiTable]]. If the string is valid, the function outputs

-- false and nil.
local function isKnownProject(prefix)
function p.getSnippets(args)
--[=[
for projectCode, projectVal in pairs(interwikiTable) do
-- This function gets user data snippets from the arguments, and from
for _, iwCode in ipairs(projectVal.iw_prefix) do
-- [[Module:InterwikiTable]]. The data is loaded as necessary and memoized
if iwCode == prefix then
-- in the snippets table for performance.
return true, projectCode
--
end
-- Snippets default to the blank string, '', so they can be used in
end
-- concatenation operations without coders having to worry about raising
end
-- errors. Because of this, the local functions snippetExists and
return false, nil
-- 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 "project" argument is absent. The default value
-- is the "snippet-project-default" 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. "wikipedia" or "wikibooks".
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 "lang" argument is absent. The default value is
-- the "snippet-lang-default" 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. ":wikt:es:".
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
end


--------------------------------------------------------------------------------
local function generateUserDataStrings(args)
-- Main functions
-- If the username is absent or blank, return an error and a tracking category.
--------------------------------------------------------------------------------
if args.user == '' or (not args.user and (not args.User or args.User == '')) then
return err('no username detected', 'No username detected')
else
u.username = args.user or args.User
end
-- Get other basic user data strings.
u.project = args.Project or args.project
u.lang = args.lang or args.Lang
if u.lang then
if mw.language.isKnownLanguageTag(u.lang) then
table.insert(trackingCategories, '[[Category:UserLinks transclusions with language parameters]]')
else
return err('"' .. u.lang .. '" is not a valid language code', 'Not a valid language code')
end
end
-- Process the project value if it is present.
if u.project then
table.insert( trackingCategories, '[[Category:UserLinks transclusions with project parameters]]' )
-- If u.project is a known project, we only need to get the project code. If the project
-- isn't known, first check whether it is a valid language code, and if not then see if it's
-- an interwiki code separated by colons, e.g. "wikt:es".
local uprojectIsKnownProject, uprojectProjectCode = isKnownProject(u.project)
if uprojectIsKnownProject then
u.projectCode = uprojectProjectCode
else
if mw.language.isKnownLanguageTag(u.project) then
u.lang = u.project
u.project = nil
else
local pref1, pref2 = mw.ustring.match( u.project, '^(%w+):(%w+)$' )
if pref1 and pref2 then
local pref1IsKnownProject, pref1ProjectCode = isKnownProject(pref1)
local pref2IsKnownProject, pref2ProjectCode = isKnownProject(pref2)
if pref1IsKnownProject
and mw.language.isKnownLanguageTag(pref2)
and interwikiTable[pref1ProjectCode].takes_lang_prefix then
u.project = pref1
u.lang = pref2
u.projectCode = pref1ProjectCode
table.insert(
trackingCategories,
'[[Category:UserLinks transclusions with project parameters containing language codes]]'
)
elseif pref2IsKnownProject
and mw.language.isKnownLanguageTag(pref1)
and interwikiTable[pref2ProjectCode].takes_lang_prefix then
u.project = pref2
u.lang = pref1
u.projectCode = pref2ProjectCode
table.insert(
trackingCategories,
'[[Category:UserLinks transclusions with project parameters containing language codes]]'
)
else
return err(
'"' .. u.project .. '" is not a valid interwiki prefix',
'Not a valid interwiki prefix'
)
end
else
return err(
'"' .. u.project .. '" is not a valid interwiki prefix',
'Not a valid interwiki prefix'
)
end
end
end
end
-- Generate the interwiki prefix. This includes colons.
if u.project or u.lang then
u.interwiki = ''
if u.project then
u.interwiki = u.interwiki .. ':' .. u.project
end
if u.lang then
u.interwiki = u.interwiki .. ':' .. u.lang
end
u.interwiki = u.interwiki .. ':'
else
u.interwiki = ''
end


local function makeInvokeFunction(funcName)
-- Generate the other helper strings.
-- Makes a function that can be accessed from #invoke. This is only required
u.usernameHtml = mw.uri.encode(u.username) -- Html-encoded username. Spaces are encoded as pluses.
-- for functions that need to access arguments.
if u.project then
return function (frame)
local prefixes = interwikiTable[u.projectCode].iw_prefix
mArguments = require('Module:Arguments')
u.projectLong = prefixes[2] or prefixes[1] -- A bit of a hack, but should find the long prefix name most of the time.
local args = mArguments.getArgs(frame)
else
return p[funcName](args)
u.projectLong = 'wikipedia'
end
end
u.toolLang = u.lang or 'en' -- set the default language for tools on the toolserver or labs.
end
end


p.main = makeInvokeFunction('_main')
local function generateTrackingCategories()

if demo == 'yes' then
function p._main(args)
return ''
-- The main function. This is the one called from [[Template:User-multi]],
else
-- via p.main.
return table.concat(trackingCategories)
local options = p.getOptions(args)
end
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
end


function p.getOptions(args)
-- This function generates a table of all available link types, with their previews.
-- Gets the options from the args table, so that we don't have to pass
-- It is used in the module documentation.
-- around the whole args table all the time.
local function getLinkTable(args)
local options = {}
demo = args.demo -- Set the demo variable.
options.isDemo = yesno(args.demo) or false
-- Generate the user data strings and return any errors.
options.toolbarStyle = yesno(args.small) and 'font-size: 90%;' or nil
local dataStringError = generateUserDataStrings(args)
options.sup = yesno(args.sup, true)
if dataStringError then
options.separator = args.separator
return dataStringError
options.span = args.span
end
return options
-- Build a table of all of the links.
local result = '<table class="wikitable plainlinks sortable">'
.. '\n<tr><th>Code</th><th>Preview</th></tr>'
for i, value in ipairs(linktypes) do
local code = value[1]
result = result .. "\n<tr><td>'''" .. code .. "'''</td><td>" .. getLink(code) .. '</td></tr>'
end
result = result .. '\n</table>'
return result
end
end


local function getSingleLink(args)
function p.getCodes(args)
-- Gets the link codes from the arguments. The codes aren't validated
demo = args.demo -- Set the demo variable.
-- at this point.
-- Generate the user data strings and return any errors.
mTableTools = maybeLoadModule('Module:TableTools')
local dataStringError = generateUserDataStrings(args)
local codes
if dataStringError then
if mTableTools then
return dataStringError
codes = mTableTools.compressSparseArray(args)
end
else
codes = {}
local linktype = args[1]
for i, code in ipairs(args) do
if not linktype then
codes[i] = code
return err('no link type specified')
end
end
end
local result = getLink(linktype)
return codes
result = result .. generateTrackingCategories()
return result
end
end


local function getLinks(args)
function p.export(codes, links, options)
demo = args.demo -- Set the demo variable.
-- Make the user link.
local userLink = links.u
-- Generate the user data strings and return any errors.

local dataStringError = generateUserDataStrings(args)
-- If we weren't passed any link codes, just return the user link.
if dataStringError then
if #codes < 1 then
return dataStringError
return userLink
end
end

-- Build the template output.
local result = makeToolbar(args) -- Get the toolbar contents.
-- Make the toolbar.
mToolbar = require('Module:Toolbar')
if result then
local toolbarArgs = {}
if args.sup then
for i, code in ipairs(codes) do
result = '<sup>' .. result .. '</sup>'
local link = links[code]
end
toolbarArgs[#toolbarArgs + 1] = link
result = '&nbsp;' .. result
end
else
toolbarArgs.style = options.toolbarStyle
result = '' -- If there are no links specified, don't return the toolbar at all.
toolbarArgs.separator = options.separator or 'dot'
end
toolbarArgs.span = options.span
result = '<span>' .. makeUserLink() .. result .. '</span>'
local toolbar = mToolbar.main(toolbarArgs)
result = result .. generateTrackingCategories()

-- Apply the sup option.
return result
if options.sup then
toolbar = '<sup>' .. toolbar .. '</sup>'
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 '&nbsp;'
return userLink .. space .. toolbar
end
end


--------------------------------------------------------------------------------
local function getExampleLinks(args)
-- Single link function
-- This function enables example output without having to specify any
--------------------------------------------------------------------------------
-- parameters to #invoke.

args.demo = 'yes'
p.single = makeInvokeFunction('_single')
args.user = 'Example'

args.User = nil
return getLinks(args)
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
end


local function makeWrapper(func)
function p.exportSingle(links, code)
-- If any errors occur, they will probably occur here. This function
return function (frame)
-- exists purely so that all the errors that will occur in p._single can
-- If called via #invoke, use the args passed into the invoking template.
-- be handled using a single pcall.
-- Otherwise, for testing purposes, assume args are being passed directly in.
if not code then
local origArgs
raiseError(
if frame == mw.getCurrentFrame() then
message('error-nolinkcode'),
origArgs = frame:getParent().args
message('error-nolinkcode-section')
for k, v in pairs(frame.args) do
)
origArgs = frame.args
end
break
return links[code]
end
else
origArgs = frame
end
-- Strip whitespace, and treat blank arguments as nil.
-- 'user', 'User', and 'separator' have different behaviour depending on
-- whether they are blank or nil, so keep them as they are.
local args = {}
for k, v in pairs(origArgs) do
v = mw.text.trim(v)
if v ~= '' or k == 'user' or k == 'User' or k == 'separator' then
args[k] = v
end
end
return func(args)
end
end
end


--------------------------------------------------------------------------------
return {
-- Link table
main = makeWrapper(getLinks),
--------------------------------------------------------------------------------
single = makeWrapper(getSingleLink),

linktable = makeWrapper(getLinkTable),
function p.linktable()
example = makeWrapper(getExampleLinks)
-- 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] < 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="wikitable plainlinks sortable"'
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] = "| '''" .. code .. "'''"
ret[#ret + 1] = '| ' .. link
end
ret[#ret + 1] = '|}'
return table.concat(ret, '\n')
end

return p

Latest revision as of 14:49, January 21, 2022

Documentation for this module may be created at Module:UserLinks/doc

--------------------------------------------------------------------------------
--                                 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.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
	
	----------------------------------------------------------------------------
	-- 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 "project" argument is absent. The default value
		-- is the "snippet-project-default" 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. "wikipedia" or "wikibooks".
		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 "lang" argument is absent. The default value is
		-- the "snippet-lang-default" 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. ":wikt:es:".
		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.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 = links.u

	-- If we weren't passed any link codes, just return the user link.
	if #codes < 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 = '<sup>' .. toolbar .. '</sup>'
	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 '&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] < 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="wikitable plainlinks sortable"'
	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] = "| '''" .. code .. "'''" 
		ret[#ret + 1] = '| ' .. link
	end
	ret[#ret + 1] = '|}'
	return table.concat(ret, '\n')
end 

return p