Difference between revisions of "Module:UserLinks"

From blackwiki
Jump to navigation Jump to search
blackwiki>Mr. Stradivarius
(Complete rewrite of the module, updated from sandbox. Now supports adding new link functions on Module:UserLinks/extra, and is configurable at Module:UserLinks/config.)
blackwiki>Mr. Stradivarius
(Undid revision 602835579 by Mr. Stradivarius (talk) revert for now, generated an unexpected script error)
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
  
function p.getLinks(snippets)
+
local function makeTalkLink()
--[=[
+
    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.
 
--
 
-- For documentation on how to add new link functions, please see
 
-- [[Module:UserLinks#Adding new links]].
 
----------------------------------------------------------------------------
 
  
function linkFunctions.u(snippets)
+
local function makeCountLink()
-- 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
 
  
function linkFunctions.t(snippets)
+
local function makeMovesLink()
-- 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
 
  
function linkFunctions.c(snippets)
+
local function makeLogsLink()
-- Contributions
+
    return '[[' .. u.interwiki .. 'Special:Log/' .. u.username .. '|logs]]'
return makeWikilink(
+
end
snippets.interwiki,
 
-1,
 
'Contributions/' .. snippets.username,
 
message('display-contributions')
 
)
 
end
 
  
function linkFunctions.ct(snippets)
+
local function makeBlockLogLink()
-- 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 = 'tools.wmflabs.org',
 
path = '/supercount/index.php',
 
query = {
 
user = snippets.username,
 
project = snippets.toolLang .. '.' .. snippets.projectLong
 
}
 
},
 
message('display-count')
 
)
 
end
 
  
function linkFunctions.m(snippets)
+
local function makeBlocksLink()
-- 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
 
  
function linkFunctions.l(snippets)
+
local function makeBlockUserLink()
-- Logs
+
    return '[[' .. u.interwiki .. 'Special:Block/' .. u.username .. '|block&nbsp;user]]'
return makeWikilink(
+
end
snippets.interwiki,
 
-1,
 
'Log/' .. snippets.username,
 
message('display-logs')
 
)
 
end
 
  
function linkFunctions.bl(snippets)
+
local function makeCentralAuthLink()
-- Block log
+
    return '[[' .. u.interwiki .. 'Special:CentralAuth/' .. u.username .. '|central&nbsp;auth]]'
return makeFullUrlLink(
+
end
snippets.interwiki,
 
-1,
 
'Log/block',
 
{page = 'User:' .. snippets.username},
 
message('display-blocklog')
 
)
 
end
 
  
function linkFunctions.bls(snippets)
+
local function makeDeletedContribsLink()
-- Blocks
+
    return '[[' .. u.interwiki .. 'Special:DeletedContributions/' .. u.username .. '|deleted&nbsp;contribs]]'
return makeWikilink(
+
end
snippets.interwiki,
 
-1,
 
'Log/block/' .. snippets.username,
 
message('display-blocks')
 
)
 
end
 
  
function linkFunctions.bu(snippets)
+
local function makeEmailLink()
-- Block user
+
    return '[[' .. u.interwiki .. 'Special:Emailuser/' .. u.username .. '|email]]'
return makeWikilink(
+
end
snippets.interwiki,
 
-1,
 
'Block/' .. snippets.username,
 
message('display-blockuser')
 
)
 
end
 
  
function linkFunctions.ca(snippets)
+
local function makeEditSummariesLink()
-- Central auth
+
    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]'
'CentralAuth/' .. snippets.username,
+
end
message('display-centralauth')
 
)
 
end
 
  
function linkFunctions.dc(snippets)
+
local function makeDeletionsLink()
-- Deleted contribs
+
    return '[[' .. u.interwiki .. 'Special:Log/delete/' .. u.username .. '|deletions]]'
return makeWikilink(
+
end
snippets.interwiki,
 
-1,
 
'DeletedContributions/' .. snippets.username,
 
message('display-deletedcontributions')
 
)
 
end
 
  
function linkFunctions.e(snippets)
+
local function makeListUserLink()
-- Email
+
    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,
 
'EmailUser/' .. snippets.username,
 
message('display-email')
 
)
 
end
 
  
function linkFunctions.es(snippets)
+
local function makeSulLink()
-- Edit summaries
+
    return '[[sulutil:' .. u.username .. '|global&nbsp;contribs]]'
return makeUrlLink(
+
end
{
 
host = 'tools.wmflabs.org',
 
path = '/xtools/editsummary/index.php',
 
query = {
 
name = snippets.username,
 
lang = snippets.toolLang,
 
wiki = snippets.projectLong
 
}
 
},
 
message('display-editsummaries')
 
)
 
end
 
  
function linkFunctions.del(snippets)
+
local function makeTargetLogsLink()
-- Deletions
+
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:Log', 'page=User:' .. u.usernameHtml)
return makeWikilink(
+
    return '[' .. tostring(url) .. ' target&nbsp;logs]'
snippets.interwiki,
+
end
-1,
 
'Log/delete/' .. snippets.username,
 
message('display-deletions')
 
)
 
end
 
  
function linkFunctions.lu(snippets)
+
local function makeEditFilterLogLink()
-- List user
+
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:AbuseLog', 'wpSearchUser=' .. u.usernameHtml)
return makeFullUrlLink(
+
    return '[' .. tostring(url) .. ' edit&nbsp;filter&nbsp;log]'
snippets.interwiki,
+
end
-1,
 
'ListUsers',
 
{limit = 1, user = snippets.username},
 
message('display-listuser')
 
)
 
end
 
  
function linkFunctions.sul(snippets)
+
local function makeProtectionsLink()
-- SUL
+
    return '[[' .. u.interwiki .. 'Special:Log/protect/' .. u.username .. '|protections]]'
return makeWikilink(
+
end
nil,
 
nil,
 
'sulutil:' .. snippets.username,
 
message('display-sul')
 
)
 
end
 
  
function linkFunctions.tl(snippets)
+
local function makeRightsLink()
-- Target logs
+
    return '[[' .. u.interwiki .. 'Special:Log/rights/' .. u.username .. '|rights]]'
return makeFullUrlLink(
+
end
snippets.interwiki,
 
-1,
 
'Log',
 
{page = mw.site.namespaces[2].name .. ':' .. snippets.username},
 
message('display-targetlogs')
 
)
 
end
 
  
function linkFunctions.efl(snippets)
+
local function makeRenamesLink()
-- Edit filter log
+
    return '[[' .. u.interwiki .. 'Special:Log/renameuser/' .. u.username .. '|renames]]'
return makeFullUrlLink(
+
end
snippets.interwiki,
 
-1,
 
'AbuseLog',
 
{wpSearchUser = snippets.username},
 
message('display-abuselog')
 
)
 
end
 
  
function linkFunctions.pr(snippets)
+
local function makeRfaLink()
-- Protections
+
    if u.project or u.lang then
return makeWikilink(
+
        table.insert( trackingCategories, '[[Category:UserLinks transclusions with unresolvable interwiki links]]' )
snippets.interwiki,
+
    end
-1,
+
    return '[[Special:PrefixIndex/Wikipedia:Requests for adminship/' .. u.username .. '|RfA]]'
'Log/protect/' .. snippets.username,
+
end
message('display-protections')
 
)
 
end
 
  
function linkFunctions.rl(snippets)
+
local function makeApiLink()
-- User rights
+
    -- 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/rights/' .. snippets.username,
+
        local domain = interwikiTable[u.projectCode].domain
message('display-rights')
+
        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
  
function linkFunctions.ren(snippets)
+
local function makeUploadsLink()
-- Renames
+
    return '[[' .. u.interwiki .. 'Special:ListFiles/' .. u.username .. '|uploads]]'
return makeWikilink(
+
end
snippets.interwiki,
 
-1,
 
'Log/renameuser/' .. snippets.username,
 
message('display-renames')
 
)
 
end
 
  
function linkFunctions.rfa(snippets)
+
----------------------------------------------------------------------------------------------
-- Requests for adminship
+
--                                   LINK FUNCTIONS END                                    --
return makeWikilink(
+
--      To enable new link functions, add the code to the "linktypes" table directly below. --
nil,
+
----------------------------------------------------------------------------------------------
-1,
 
'PrefixIndex/' .. message('page-rfa') .. '/' .. snippets.username,
 
message('display-rfa')
 
)
 
end
 
  
function linkFunctions.api(snippets)
+
local linktypes = {
-- API user data
+
    {'t'  , makeTalkLink},
return makeUrlLink(
+
    {'c'  , makeContribsLink},
{
+
    {'ct'  , makeCountLink},
host = snippets.fullDomain,
+
    {'m'  , makeMovesLink},
path = '/w/api.php',
+
    {'l'  , makeLogsLink},
query = {
+
    {'bl'  , makeBlockLogLink},
action = 'query',
+
    {'bls' , makeBlocksLink},
list = 'users',
+
    {'bu'  , makeBlockUserLink},
usprop = 'editcount',
+
    {'ca'  , makeCentralAuthLink},
ususers = snippets.username
+
    {'dc'  , makeDeletedContribsLink},
}
+
    {'e'  , makeEmailLink},
},
+
    {'es'  , makeEditSummariesLink},
message('display-api')
+
    {'del' , makeDeletionsLink},
)
+
    {'lu' , makeListUserLink},
end
+
    {'sul' , makeSulLink},
 +
    {'tl' , makeTargetLogsLink},
 +
    {'efl' , makeEditFilterLogLink},
 +
    {'pr' , makeProtectionsLink},
 +
    {'rl'  , makeRightsLink},
 +
    {'ren' , makeRenamesLink},
 +
    {'rfa' , makeRfaLink},
 +
    {'api' , makeApiLink},
 +
    {'up'  , makeUploadsLink}
 +
}
  
function linkFunctions.up(snippets)
+
local function getLink(linktype)
-- Uploads
+
    local linkNumber
return makeWikilink(
+
    for i, value in ipairs(linktypes) do
snippets.interwiki,
+
        if value[1] == linktype then
-1,
+
            linkNumber = i
'ListFiles/' .. snippets.username,
+
            break
message('display-uploads')
+
        end
)
+
    end
end
+
    if not linkNumber then
+
        return err('"' .. linktype .. '" is not a valid link code', 'Not a valid link code')
----------------------------------------------------------------------------
+
    end
-- End of link functions
+
    local result = linktypes[linkNumber][2]()
----------------------------------------------------------------------------
+
    if type(result) ~= 'string' then
 +
        return err(
 +
            'the function for code "' .. linktype .. '" did not return a string value',
 +
            'Function did not return a string value'
 +
        )
 +
    end
 +
    return result
 +
end
  
-- Define the metatable that memoizes the link functions, and fetches link
+
local function makeToolbar(args)
-- functions from [[Module:UserLinks/extra]] if necessary.
+
    local targs = {}
 
+
    local numArgsExist = false
-- Lazily initialise the extraLinkFunctions table. We only want to load
+
    for k, v in pairs(args) do
-- [[Module:UserLinks/extra]] as necessary, so it has a low transclusion
+
        if type(k) == 'number' then
-- count.
+
            numArgsExist = true
local extraLinkFunctions
+
            targs[k] = getLink(v)
 
+
        end
-- Define functions for shared code in the metatable.
+
    end
local function validateCode(code)
+
    targs.style = args.small and 'font-size: 90%;'
-- Checks whether code is a valid link code - i.e. checks that it is a
+
    targs.separator = args.separator or 'dot'
-- string and that it is not the blank string. Returns the code if
+
   
-- the check passes, and nil if not.
+
    if numArgsExist == false then
if type(code) == 'string' and code ~= '' then
+
        return nil -- Don't return a toolbar if no numeric arguments exist.
return code
+
    else
else
+
        return ToolbarBuilder.main(targs)
return nil
+
    end
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.
function p.getSnippets(args)
+
local function isKnownProject(prefix)
--[=[
+
     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
--
 
-- 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
 
         end
 
     end
 
     end
     return nil, nil
+
     return false, 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
  
function p.getCodes(args)
+
local function getSingleLink(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
  
function p.export(codes, links, options)
+
local function getLinks(args)
-- Make the user link.
+
    demo = args.demo -- Set the demo variable.
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.
-- Make the toolbar.
+
    local result = makeToolbar(args) -- Get the toolbar contents.
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 and return the result.
+
    return result
if options.sup then
 
toolbar = '<sup>' .. toolbar .. '</sup>'
 
end
 
return userLink .. '&nbsp;' .. 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
function p._single(args)
+
    return getLinks(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
  
function p.exportSingle(links, code)
+
local function makeWrapper(func)
-- 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
 

Revision as of 08:29, 5 April 2014

This module generates links about a given user. It is used to generate templates such as {{user}}, {{user5}}, and {{admin}}, usually through its wrapper template {{user-multi}}.

Functions

Main

The main function implements the {{user-multi}} template. It generates a list of links about a given user. Please see the template page for documentation.

Single

The single function generates a single link about a given user. See {{user-multi/link}} for documentation.

Link table

The linktable function generates a wikitext table containing the possible link codes that can be used with the main and single functions, along with example output for each code. It is used on the documentation pages at Template:User-multi/doc and Template:User-multi/link/doc.

It is displayed with the following code:

{{#invoke:UserLinks|linktable}}

Porting to other wikis

If you want to use this module on another wiki, there are a few modules that you must also copy across, and some that can be used but are not essential.

Required modules:

Optional modules:

  • Module:UserLinks/extra - used for testing new link functions before they are moved to the main module.
  • Module:Category handler - if an error occurs, and this module is present, pages are not categorised if they match the module's blacklist.

After you have copied over the necessary modules, you should adjust the configuration settings in Module:UserLinks/config for your language and for your wiki's setup.


local ToolbarBuilder = require('Module:Toolbar')
local interwikiTable = mw.loadData("Module:InterwikiTable")

local u = {} -- Table for user-data helper strings.
local trackingCategories = {} -- Table for storing the tracking categories.
local demo

-- Define a custom error message for this module.
local function err(msg, section)
    local help
    if section then
        help = ' ([[Template:User-multi#' .. section .. '|help]])'
    else
        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

----------------------------------------------------------------------------------------------
--      To add more link types, write a function that produces an individual link, and put  --
--      it at the bottom of the list below. Then, add a link code for your function to the  --
--      "linktypes" table. Try and make the code three letters or less. There are a number  --
--      of helper strings available for writing the functions:                       --
--                                                                                          --
--      u.username          The plain username. If the username is not present then the     --
--                          module returns an error.                                        --
--      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.                                                                     --
----------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------
--                                LINK FUNCTIONS START                                      --
----------------------------------------------------------------------------------------------

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

local function makeTalkLink()
    return '[[' .. u.interwiki .. 'User talk:' .. u.username .. '|talk]]'
end

local function makeContribsLink()
    return '[[' .. u.interwiki .. 'Special:Contributions/' .. u.username .. '|contribs]]'
end

local function makeCountLink()
    return '[//tools.wmflabs.org/supercount/index.php?user=' .. u.usernameHtml
        .. '&project=' .. u.toolLang .. '.' .. u.projectLong
        .. ' count]'
end

local function makeMovesLink()
    return '[[' .. u.interwiki .. 'Special:Log/move/' .. u.username .. '|page&nbsp;moves]]'
end

local function makeLogsLink()
    return '[[' .. u.interwiki .. 'Special:Log/' .. u.username .. '|logs]]'
end

local function makeBlockLogLink()
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:Log/block', 'page=User:' .. u.usernameHtml)
    return '[' .. tostring(url) .. ' block&nbsp;log]'
end

local function makeBlocksLink()
    return '[[' .. u.interwiki .. 'Special:Log/block/' .. u.username .. '|blocks]]'
end

local function makeBlockUserLink()
    return '[[' .. u.interwiki .. 'Special:Block/' .. u.username .. '|block&nbsp;user]]'
end

local function makeCentralAuthLink()
    return '[[' .. u.interwiki .. 'Special:CentralAuth/' .. u.username .. '|central&nbsp;auth]]'
end

local function makeDeletedContribsLink()
    return '[[' .. u.interwiki .. 'Special:DeletedContributions/' .. u.username .. '|deleted&nbsp;contribs]]'
end

local function makeEmailLink()
    return '[[' .. u.interwiki .. 'Special:Emailuser/' .. u.username .. '|email]]'
end

local function makeEditSummariesLink()
    return '[http://tools.wmflabs.org/xtools/editsummary/index.php?name=' .. u.usernameHtml
        .. '&lang=' .. u.toolLang
        .. '&wiki=' .. u.projectLong
        .. ' edit&nbsp;summaries]'
end

local function makeDeletionsLink()
    return '[[' .. u.interwiki .. 'Special:Log/delete/' .. u.username .. '|deletions]]'
end

local function makeListUserLink()
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:ListUsers', 'limit=1&username=' .. u.usernameHtml)
    return '[' .. tostring(url) .. ' list&nbsp;user]'
end

local function makeSulLink()
    return '[[sulutil:' .. u.username .. '|global&nbsp;contribs]]'
end

local function makeTargetLogsLink()
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:Log', 'page=User:' .. u.usernameHtml)
    return '[' .. tostring(url) .. ' target&nbsp;logs]'
end

local function makeEditFilterLogLink()
    local url = mw.uri.fullUrl(u.interwiki .. 'Special:AbuseLog', 'wpSearchUser=' .. u.usernameHtml)
    return '[' .. tostring(url) .. ' edit&nbsp;filter&nbsp;log]'
end

local function makeProtectionsLink()
    return '[[' .. u.interwiki .. 'Special:Log/protect/' .. u.username .. '|protections]]'
end

local function makeRightsLink()
    return '[[' .. u.interwiki .. 'Special:Log/rights/' .. u.username .. '|rights]]'
end

local function makeRenamesLink()
    return '[[' .. u.interwiki .. 'Special:Log/renameuser/' .. u.username .. '|renames]]'
end

local function makeRfaLink()
    if u.project or u.lang then
        table.insert( trackingCategories, '[[Category:UserLinks transclusions with unresolvable interwiki links]]' )
    end
    return '[[Special:PrefixIndex/Wikipedia:Requests for adminship/' .. u.username .. '|RfA]]'
end

local function makeApiLink()
    -- Find the full domain, as the API can't be accessed through the interwiki system.
    local fulldomain
    local lang = u.lang or 'en'
    if u.projectCode then
        local domain = interwikiTable[u.projectCode].domain
        local takesLangPrefix = interwikiTable[u.projectCode].takes_lang_prefix
        if not takesLangPrefix then
            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()
    return '[[' .. u.interwiki .. 'Special:ListFiles/' .. u.username .. '|uploads]]'
end

----------------------------------------------------------------------------------------------
--                                   LINK FUNCTIONS END                                     --
--      To enable new link functions, add the code to the "linktypes" table directly below. --
----------------------------------------------------------------------------------------------

local linktypes = {
    {'t'   , makeTalkLink},
    {'c'   , makeContribsLink},
    {'ct'  , makeCountLink},
    {'m'   , makeMovesLink},
    {'l'   , makeLogsLink},
    {'bl'  , makeBlockLogLink},
    {'bls' , makeBlocksLink},
    {'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)
    local linkNumber
    for i, value in ipairs(linktypes) do
        if value[1] == linktype then
            linkNumber = i
            break
        end
    end
    if not linkNumber then
        return err('"' .. linktype .. '" is not a valid link code', 'Not a valid link code')
    end
    local result = linktypes[linkNumber][2]()
    if type(result) ~= 'string' then
        return err(
            'the function for code "' .. linktype .. '" did not return a string value',
            'Function did not return a string value'
        )
    end
    return result
end

local function makeToolbar(args)
    local targs = {}
    local numArgsExist = false
    for k, v in pairs(args) do
        if type(k) == 'number' then
            numArgsExist = true
            targs[k] = getLink(v)
        end
    end
    targs.style = args.small and 'font-size: 90%;'
    targs.separator = args.separator or 'dot'
    
    if numArgsExist == false then
        return nil -- Don't return a toolbar if no numeric arguments exist.
    else
        return ToolbarBuilder.main(targs)
    end
end

-- This function finds whether a string is a valid interwiki project prefix.
-- 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)
    for projectCode, projectVal in pairs(interwikiTable) do
        for _, iwCode in ipairs(projectVal.iw_prefix) do
            if iwCode == prefix then
                return true, projectCode
            end
        end
    end
    return false, nil
end

local function generateUserDataStrings(args)
    -- 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

    -- Generate the other helper strings.
    u.usernameHtml = mw.uri.encode(u.username) -- Html-encoded username. Spaces are encoded as pluses.
    if u.project then
        local prefixes = interwikiTable[u.projectCode].iw_prefix
        u.projectLong = prefixes[2] or prefixes[1] -- A bit of a hack, but should find the long prefix name most of the time.
    else
        u.projectLong = 'wikipedia'
    end
    u.toolLang = u.lang or 'en' -- set the default language for tools on the toolserver or labs.
end

local function generateTrackingCategories()
    if demo == 'yes' then
        return ''
    else
        return table.concat(trackingCategories)
    end
end

-- This function generates a table of all available link types, with their previews.
-- It is used in the module documentation.
local function getLinkTable(args)
    demo = args.demo -- Set the demo variable.
    -- Generate the user data strings and return any errors.
    local dataStringError = generateUserDataStrings(args)
    if dataStringError then
        return dataStringError
    end
    
    -- 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

local function getSingleLink(args)
    demo = args.demo -- Set the demo variable.
    -- Generate the user data strings and return any errors.
    local dataStringError = generateUserDataStrings(args)
    if dataStringError then
        return dataStringError
    end
    
    local linktype = args[1]
    if not linktype then 
        return err('no link type specified')
    end
    local result = getLink(linktype)
    result = result .. generateTrackingCategories()
    return result
end

local function getLinks(args)
    demo = args.demo -- Set the demo variable.
    -- Generate the user data strings and return any errors.
    local dataStringError = generateUserDataStrings(args)
    if dataStringError then
        return dataStringError
    end    
    
    -- Build the template output.
    local result = makeToolbar(args) -- Get the toolbar contents.
    if result then
        if args.sup then
            result = '<sup>' .. result .. '</sup>'
        end
        result = '&nbsp;' .. result
    else
        result = '' -- If there are no links specified, don't return the toolbar at all.
    end
    result = '<span>' .. makeUserLink() .. result .. '</span>'
    result = result .. generateTrackingCategories()
    
    return result
end

local function getExampleLinks(args)
    -- This function enables example output without having to specify any
    -- parameters to #invoke.
    args.demo = 'yes'
    args.user = 'Example'
    args.User = nil
    return getLinks(args)
end

local function makeWrapper(func)
    return function (frame)
        -- If called via #invoke, use the args passed into the invoking template.
        -- Otherwise, for testing purposes, assume args are being passed directly in.
        local origArgs
        if frame == mw.getCurrentFrame() then
            origArgs = frame:getParent().args
            for k, v in pairs(frame.args) do
                origArgs = frame.args
                break
            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

return {
    main = makeWrapper(getLinks),
    single = makeWrapper(getSingleLink),
    linktable = makeWrapper(getLinkTable),
    example = makeWrapper(getExampleLinks)
}