Modul:UKB: Forskjell mellom sideversjoner

Fra Wikisida.no
Hopp til navigering Hopp til søk
m (Én sideversjon ble importert)
m (Én sideversjon ble importert)
 
(Én mellomliggende versjon av en annen bruker er ikke vist)
Linje 1: Linje 1:
require('strict')
--[ Localization and config ]-----------------------------------------------------------------


local messages = {
local p = {}
['and'] = 'og',
['or'] = 'eller',
['page_at_site'] = '%(page)s på %(site)s',
['argument_missing'] = 'Argument mangler: %s',
['anon_argument_missing'] = 'Ingen %s ble angitt',
['invalid_criterion'] = '«%s» er ikke et gyldig kriterium',
['invalid_rule'] = '«%s» er ikke en gyldig poengregel',


local TNT = require('Module:TNT')
-- Criteria
local I18NDATASET = 'I18n/UKB.tab'
local getArgs = require('Module:Arguments').getArgs


--- Get a localized message.
['templates'] = 'maler',
-- @param key The message key
['templates_criterion_singular'] = 'merket med %s',
-- @param ... Parameters to be passed to the message ($1, $2, etc.)
['templates_criterion_plural'] = 'merket med %s',
-- @return localized string
local function msg( key, ... )
return TNT.format( I18NDATASET, key, ... )
end


--- Reverse a mapping to get a list of localized names => canonical names
['categories'] = 'kategorier',
-- @param mapping A table containing key-value pairs where the key is the canonical name and the value is an array table of aliases
['categories_criterion_singular'] = 'ligger i kategorien %s',
-- @return A table of localized names => canonical names
['categories_criterion_plural'] = 'ligger i minst en av kategoriene %s',
local function mappingReverser(mapping)
['categories_criterion_ignore'] = ', men ikke i %s',
local ret = {}
for canonical, synonyms in pairs(mapping) do
for _, synonym in ipairs(synonyms) do
ret[synonym] = canonical
end
local keyIsPresent, translations = pcall(msg, 'arg-' .. canonical)
if keyIsPresent then
translations = mw.text.split(translations, '|')
for _, translation in ipairs(translations) do
ret[translation] = canonical
end
end
end
return ret
end


--- Get the argument mapping for a type of item
['backlinks'] = 'tilbakelenker',
-- @param itemType The mapping subtype to get. Either 'criteria' or 'rules'
['backlinks_criterion_singular'] = 'lenkes fra %s',
-- @param returnType Which mapping to get; 'canonical' or 'translated'
['backlinks_criterion_plural'] = 'lenkes fra %s',
-- @return A table of mappings
local function getArgumentMapping(itemType, returnType)
-- if a new argument is added, it should also be added to the i18n module
-- in [[c:Data:I18n/UKB.tab]]
local argumentMapping = {
['criteria'] = {
['backlinks'] = { 'backlink' },
['bytes'] = { 'byte' },
['categories'] = { 'category' },
['forwardlinks'] = { 'forwardlink' },
['new'] = {},
['existing'] = {},
['namespaces'] = { 'namespace' },
['pages'] = { 'page' },
['sparql'] = {},
['stub'] = {}, -- deprecated, not in i18n
['templates'] = { 'template' }
},
['rules'] = {
['bytes'] = { 'byte' },
['bytebonus'] = {},
['categoryremoval'] = {},
['edit'] = {},
['eligiblepage'] = {},
['extlink'] = { 'exlink', 'externallink' },
['image'] = { 'images' },
['listbyte'] = { 'listbytes' },
['newpage'] = {},
['newredirect'] = {},
['reference'] = { 'ref' },
['section'] = {},
['templateremoval'] = {},
['wikidata'] = {},
['word'] = { 'words' },
['wordbonus'] = {}
},
['modifiers'] = {
['aliases'] = {},
['all'] = {},
['description'] = {},
['descriptions'] = {},
['distinct'] = {},
['ignore'] = {},
['initialimagelimit'] = {},
['labels'] = {},
['max'] = {},
['ownimage'] = {},
['properties'] = {},
['query'] = {},
['requirereference'] = { 'require reference', 'require_reference' },
['redirects'] = { 'redirect' },
['site'] = {},
}
}


if returnType == 'canonical' then
['forwardlinks'] = 'framlenker',
return argumentMapping[itemType]
['forwardlinks_criterion_singular'] = 'lenker til %s',
end
['forwardlinks_criterion_plural'] = 'lenker til %s',


local translatedMap = {
['pages'] = 'sider',
['criteria'] = mappingReverser(argumentMapping.criteria),
['pages_criterion_singular'] = '%s',
['rules'] = mappingReverser(argumentMapping.rules),
['pages_criterion_plural'] = '%s',
['modifiers'] = mappingReverser(argumentMapping.modifiers)
}


return translatedMap[itemType]
['sparql_criterion'] = 'har et wikidata-element som matcher [%(queryLink)s denne SPARQL-spørringen]',
end
['sparql_criterion_with_explanation'] = '%(description)s ([%(queryLink)s Wikidata-spørring])',

['bytes_criterion'] = 'utvides med minst %s byte',

['namespaces_criterion_singular'] = 'er en %s',
['namespaces_criterion_plural'] = 'er en %s',
['article'] = 'artikkel',

['new_criterion'] = 'opprettes i løpet av konkurransen',
['new_criterion_with_redirects'] = 'opprettes i løpet av konkurransen (inkludert omdirigeringer)',

['existing_criterion'] = 'ble opprettet før konkurransen startet',

-- Rules


['rule_site'] = '%(baserule)s på %(site)s',
['base_rule_max'] = '%(baserule)s, men maks %(maxpoints)s poeng per side',

['custom_rule'] = 'Det gis %(points)s poeng for %(description)s',
['newpage_rule'] = 'Det gis %(points)s poeng for opprettelse av side (ikke omdirigeringsside)',
['newredirect_rule'] = 'Det gis %(points)s poeng for opprettelse av omdirigeringsside',
['page_rule'] = 'Det gis %(points)s poeng for hver kvalifiserte side',
['edit_rule'] = 'Det gis %(points)s poeng for hver redigering',
['byte_rule'] = 'Det gis %(points)s poeng for hver tilføyde byte',
['listbyte_rule'] = 'Det gis %(points)s poeng for hver tilføyde byte i en listeartikkel',
['word_rule'] = 'Det gis %(points)s poeng for hvert tilføyde ord i brødtekst (ikke i maler, tabeller, o.l.)',

['image_rule'] = 'Det gis %(points)s poeng for hvert tilføyde bilde',
['image_rule_limited'] = 'Det gis %(points)s poeng for hvert tilføyde bilde til sider som hadde maks %(initialimagelimit)s bilder fra før',
['image_rule_own'] = '(%(ownimage)s for egenopplastede)',

['reference_rule'] = 'Det gis %(points)s poeng for hver tilføyde kilde og %(refpoints)s for hver henvisning til eksisterende (navngitt) kilde',

['templateremoval_rule'] = 'Det gis %(points)s poeng for fjerning av %(templates)s',
['categoryremoval_rule'] = 'Det gis %(points)s poeng for fjerning av %(categories)s',
['exlink_rule'] = 'Det gis %(points)s poeng for innlegging av [[WP:EL|ekstern lenke]]',

['section_rule'] = 'Det gis %(points)s poeng for tilføyelse av seksjonen «%(sections)s»',
['section_rule_desc'] = 'Det gis %(points)s poeng for tilføyelse av %(description)s',

['wikidata_rule_first'] = 'Det gis %(points)s poeng for tilføyelse av %(thing)s på elementer som ikke har dette fra før',
['wikidata_rule_all'] = 'Det gis %(points)s poeng for hver tilføyde %(thing)s',
['wikidata_rule_require_reference'] = '(kun referansebelagte utsagn teller)',
['properties'] = 'egenskaper',
['labels'] = 'etiketter',
['aliases'] = 'alias',
['descriptions'] = 'beskrivelser',
['label'] = 'Wikidata-etikett',
['alias'] = 'Wikidata-alias',
['description'] = 'Wikidata-beskrivelse',

['bytebonus_rule'] = 'Det gis %(points)s bonuspoeng når det tilføres mer enn %(bytes)s bytes til en side',
['wordbonus_rule'] = 'Det gis %(points)s bonuspoeng når det tilføres mer enn %(words)s ord til en side',
}

local config = {
['decimal_separator'] = ',',
['template_link_template'] = 'Mal',
['error_message_template'] = 'Feil',
-- Map localized argument values for the criterion template
['criteria'] = {
['ny'] = 'new',
['eksisterende'] = 'existing',
['stubb'] = 'stub', -- deprecated
['bytes'] = 'bytes',
['navnerom'] = 'namespaces',
['kategori'] = 'categories',
['mal'] = 'templates',
['tilbakelenke'] = 'backlinks',
['fremlenke'] = 'forwardlinks',
['enkeltsider'] = 'pages',
['sparql'] = 'sparql',
['distinct'] = 'distinct',
},
-- Localized argument values for the rule template
['rules'] = {
['ny'] = 'newpage',
['omdirigering'] = 'newredirect',
['kvalifisert'] = 'page',
['endring'] = 'edit',
['stubb'] = 'stubremoval',
['byte'] = 'byte',
['listebyte'] = 'listbyte',
['ord'] = 'word',
['bilde'] = 'image',
['ref'] = 'reference',
['bytebonus'] = 'bytebonus',
['ordbonus'] = 'wordbonus',
['malfjerning'] = 'templateremoval',
['seksjon'] = 'section',
['kategorifjerning'] = 'categoryremoval',
['ekstern lenke'] = 'exlink',
['wikidata'] = 'wikidata'
}
}

local category_prefix = {
['smn'] = 'smn:Luokka',
['se'] = 'se:Kategoriija',
['nn'] = 'nn:Kategori',
['no'] = 'Kategori',
['commons'] = 'commons:Category',
['default'] = 'Kategori'
}


--[ Helper methods ] ------------------------------------------------------------------
--[ Helper methods ] ------------------------------------------------------------------


--- Make an error string
--[[ Named Parameters with Formatting Codes
-- @tparam string text Text to be wrapped in an error class
Source: <http://lua-users.org/wiki/StringInterpolation>, author:RiciLake ]]
-- @treturn string The text wrapped in an error class
local function sprintf(s, tab)
local function makeErrorString(text)
return (s:gsub('%%%((%a%w*)%)([-0-9%.]*[cdeEfgGiouxXsq])',
local html = mw.html.create('strong')
function(k, fmt) return tab[k] and ("%"..fmt):format(tab[k]) or
:addClass('error')
'%('..k..')'..fmt end))
:wikitext(text)
return tostring(html)
end
end


--- Get an error string
local function make_error(template, arg)
-- @tparam string key A message key (from i18n)
return string.format(
-- @tparam string arg An argument to pass along to the message function
'{{%s|%s}}',
-- @treturn string An error message
config['error_message_template'],
local function getErrorString(key, arg)
string.format(messages[template], arg)
return makeErrorString(msg(key, arg))
)
end
end


--- Parse and translate anonymous and named arguments
local function parse_args(frame)
-- @tparam table frame A frame object
local args = {}
-- @tparam string|nil itemType An item type to return ('criteria', 'rules' or nil)
local kwargs = {}
for k, v in pairs(frame.args) do
-- @treturn table A table of anonymous arguments (args)
-- @treturn table A table of named arguments (kwargs)
v = mw.text.trim(frame:preprocess(v))
local function parseArgs(frame, itemType, translate)
if v ~= '' then
local args = {}
if type(k) == 'number' then
local kwargs = {}
args[k] = v
local canonicalMap = getArgumentMapping(itemType, 'translated')
else
if itemType == nil then
kwargs[k] = v
canonicalMap = {}
end
end
end
local kwargsMap = getArgumentMapping('modifiers', 'translated')
end
for k, v in pairs(getArgs(frame)) do
return args, kwargs
v = mw.text.trim(frame:preprocess(v))
if v ~= '' then
if type(k) == 'number' then
if k == 1 and canonicalMap[v] ~= nil and translate then
args[1] = canonicalMap[v]
else
args[k] = v
end
else
if kwargsMap[k] ~= nil and translate then
kwargs[kwargsMap[k]] = v
else
kwargs[k] = v
end
end
end
end
return args, kwargs
end
end


--- Turn an array table into a string in list form
local function shift_args(in_args)
-- @tparam table items An array of items
local args = {}
-- @tparam string itemType Maybe unnecessary?
for i, v in ipairs(in_args) do
-- @tparam string word The strings 'or' or 'and' (representing i18n message keys)
if i > 1 then
-- @treturn string A string with the table returned as a list
args[i - 1] = v
local function listify(items, itemType, word)
end
word = word or 'or'
end
if #items == 0 then
return in_args[1], args
return getErrorString('anon-argument-missing', itemType)
end
if #items == 1 then
return items[1]
end
return mw.text.listToText(items, ', ', ' ' .. msg(word) .. ' ' )
end
end


--- Get link data for a link to a page in a specific namespace
-- @tparam table frame A frame object
-- @tparam string ns A canonical (English) namespace name; 'Template' and 'Category' supported
-- @tparam string page A page name
-- @treturn table A table containing: language code, link target and page name
local function makeNsLink(frame, ns, page)
local linkTarget
local nsNumbers = {
['Template'] = 10,
['Category'] = 14
}
local lang, pageName = mw.ustring.match(page, '^([a-z]+):(.+)$') -- FIXME: Better language code detection
if lang then
-- English namespace name is guaranteed to work, avoids need to maintain
-- lists of namespace names in the module
linkTarget = mw.ustring.format(':%s:%s:%s', lang, ns, pageName)
else
linkTarget = mw.ustring.format(':%s:%s', frame:callParserFunction('ns', nsNumbers[ns]), page)
end
return {
['lang'] = lang,
['linkTarget'] = linkTarget,
['pageName'] = pageName or page
}
end


--- Make a link to a single template, wrapped in curly brace syntax
local function format_plural(items, item_type)
-- @tparam table frame A frame object
if #items == 0 then
-- @tparam template Name of a template (optionally with an interlanguage prefix)
return make_error('anon_argument_missing', messages[item_type])
-- @treturn string An HTML string linking to the template in question
end
local function makeTemplateLink(frame, template)
if #items == 1 then
local nsLink = makeNsLink(frame, 'Template', template)
return items[1]
local wikitext = mw.text.nowiki('{{') .. mw.ustring.format('[[%s|%s]]', nsLink['linkTarget'], nsLink['pageName']) .. mw.text.nowiki('}}')
end
local html = mw.html.create('span')
return mw.text.listToText(items, ', ', ' ' .. messages['or'] .. ' ')
:addClass('template-link')
:css('font-family', 'monospace,monospace')
:wikitext(wikitext)
return tostring(html)
end
end


--- Make a link to a single category
local function format_plural_criterion(items, item_type)
-- @tparam table frame A frame object
local value = format_plural(items, item_type)
-- @tparam category Name of a category (optionally with an interlanguage prefix)
if #items == 0 then
-- @treturn string An HTML string linking to the category in question
return value
local function makeCategoryLink(frame, category)
end
local nsLink = makeNsLink(frame, 'Category', category)
if #items == 1 then
return string.format(messages[item_type .. '_criterion_singular'], value)
return mw.ustring.format('[[%s|%s]]', nsLink['linkTarget'], nsLink['pageName'])
end
return string.format(messages[item_type .. '_criterion_plural'], value)
end
end


--- Make a list of templates
local function make_template_list(args)
-- @tparam table frame A frame object
-- @tparam table args An array of template names (optionally with interlanguage prefixes)
-- @treturn table A table of template links
local function makeTemplateList(frame, args)
local templates = {}
local templates = {}
for i, v in ipairs(args) do
for i, v in ipairs(args) do
table.insert(templates, makeTemplateLink(frame, v))
local lang, link = string.match(v, '^([a-z]+):(.+)$')
if lang then
table.insert(templates, string.format('{{%s|%s|%s}}', config['template_link_template'], link, lang))
else
table.insert(templates, string.format('{{%s|%s}}', config['template_link_template'], v))
end
end
end
setmetatable(templates, {
__tostring = function(self)
return listify(templates, 'templates')
end
})
return templates
return templates
end
end


--- Make a list of categories
local function make_category_link(v)
-- @tparam table frame A frame object
local lang = 'default'
-- @tparam table args An array of category names (optionally with interlanguage prefixes)
local name = v
-- @treturn table A table of category links
local m, n = string.match(v, '^([a-z]+):(.+)$')
local function makeCategoryList(frame, args)
if m then
lang = m
local categories = {}
name = n
end
return string.format('[[:%s:%s|%s]]', category_prefix[lang], name, name)
end

local function make_category_list(args)
local category_links = {}
for i, v in ipairs(args) do
for i, v in ipairs(args) do
v = mw.text.trim(v)
v = mw.text.trim(v)
if v ~= '' then
if v ~= '' then
table.insert(category_links, make_category_link(v))
table.insert(categories, makeCategoryLink(frame, v))
end
end
end
end
setmetatable(categories, {
return category_links
__tostring = function(self)
return listify(categories, 'categories')
end
})
return categories
end
end


--- Make a list of templates
local function pagelist(args)
-- @tparam table args An array of page names (optionally with interlanguage prefixes)
local r = {}
-- @treturn table A table of page links
local function makePageList(args)
local pages = {}
for i, v in ipairs(args) do
for i, v in ipairs(args) do
v = mw.text.trim(v)
v = mw.text.trim(v)
Linje 248: Linje 272:
local lang, page = string.match(v, '^([a-z]+):(.+)$')
local lang, page = string.match(v, '^([a-z]+):(.+)$')
if lang then
if lang then
table.insert(r, string.format('[[:%s:%s|%s]]', lang, page, page))
table.insert(pages, string.format('[[:%s:%s|%s]]', lang, page, page))
else
else
table.insert(r, string.format('[[:%s]]', v))
table.insert(pages, string.format('[[:%s]]', v))
end
end
end
end
end
end
setmetatable(pages, {
return r
__tostring = function(self)
return listify(pages, 'pages')
end
})
return pages
end
end


--- Make a list of namespaces
local function nslist(args)
-- @tparam table args An array of namespace IDs
local r = {}
-- @treturn table A table of namespace names
local namespaceName = messages['article']
local function makeNsList(args)
for i, namespaceId in ipairs(args) do
local namespaces = {}
local namespaceName = msg('article')
for _, namespaceId in ipairs(args) do
namespaceId = mw.text.trim(namespaceId)
namespaceId = mw.text.trim(namespaceId)
if namespaceId ~= '' then
if namespaceId ~= '' then
Linje 266: Linje 298:
namespaceName = '{{lc:{{ns:' .. namespaceId .. '}}}}'
namespaceName = '{{lc:{{ns:' .. namespaceId .. '}}}}'
end
end
table.insert(r, namespaceName)
table.insert(namespaces, namespaceName)
end
end
end
end
setmetatable(namespaces, {
return r
__tostring = function(self)
return listify(namespaces, 'namespaces')
end
})
return namespaces
end
end


Linje 276: Linje 313:
local criterion = {}
local criterion = {}


--- Formatter function for the backlinks criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.backlinks(args, kwargs, frame)
function criterion.backlinks(args, kwargs, frame)
local pageList = makePageList(args)
return format_plural_criterion(pagelist(args), 'backlinks')
return msg('criterion-backlinks', #pageList, tostring(pageList))
end
end


--- Formatter function for the bytes criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.bytes(args, kwargs, frame)
function criterion.bytes(args, kwargs, frame)
return string.format(messages['bytes_criterion'], args[1])
return msg('criterion-bytes', args[1])
end
end


--- Formatter function for the categories criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.categories(args, kwargs, frame)
function criterion.categories(args, kwargs, frame)
local categoryList = makeCategoryList(frame, args)
local msg = format_plural_criterion(make_category_list(args), 'categories')
local ret = msg('criterion-categories', #categoryList, tostring(categoryList))


if args.ignore ~= nil then
if kwargs.ignore ~= nil then
r = mw.text.split(args.ignore, ',')
local ignoredCats = mw.text.split(kwargs.ignore, ',')
ignoredCats = makeCategoryList(frame, ignoredCats)
for i, v in ipairs(r) do
ret = ret .. msg('categories-except', #ignoredCats, tostring(ignoredCats))
v = mw.text.trim(v)
end
r[i] = make_category_link(v)
end
msg = msg .. string.format(messages['category_criterion_ignore'], mw.text.listToText(r, ', ', ' ' .. messages['or'] .. ' '))
end


return msg
return ret
end
end


--- Formatter function for the existing criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.existing(args, kwargs, frame)
function criterion.existing(args, kwargs, frame)
return messages['existing_criterion']
return msg('criterion-existing')
end
end


--- Formatter function for the forwardlinks criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.forwardlinks(args, kwargs, frame)
function criterion.forwardlinks(args, kwargs, frame)
local pages = makePageList(args)
return format_plural_criterion(pagelist(args), 'forwardlinks')
return msg('criterion-forwardlinks', #pages, tostring(pages))
end
end


--- Formatter function for the namespaces criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.namespaces(args, kwargs, frame)
function criterion.namespaces(args, kwargs, frame)
local site = kwargs.site
local nsList = makeNsList(args)
local message
local msg = format_plural_criterion(nslist(args, site), 'namespaces')

if site ~= nil then
if #nsList == 1 and args[1] == '0' then
return sprintf(messages['page_at_site'], {
message = msg('criterion-namespace-0')
['page'] = msg,
else
['site'] = string.format('[https://%s %s]', site, site),
message = msg('criterion-namespace', #nsList, tostring(nsList))
})
end
end

return msg
if kwargs.site ~= nil then
return msg('page-at-site', message, mw.ustring.format('[https://%s %s]', kwargs.site, kwargs.site))
end

return message
end
end


--- Formatter function for the new page criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.new(args, kwargs, frame)
function criterion.new(args, kwargs, frame)
if kwargs.redirects ~= nil then
local msg = messages['new_criterion']
return msg('criterion-new-with-redirects')
if kwargs.redirects ~= nil then
end
msg = messages['new_criterion_with_redirects']
return msg('criterion-new')
end
return msg
end
end


--- Formatter function for the pages (page list) criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.pages(args, kwargs, frame)
function criterion.pages(args, kwargs, frame)
local pages = makePageList(args)
return format_plural_criterion(pagelist(args), 'pages')
return msg('criterion-pages', #pages, tostring(pages))
end
end


--- Formatter function for the SPARQL criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.sparql(args, kwargs, frame)
function criterion.sparql(args, kwargs, frame)
local query = ''
local query = ''
Linje 338: Linje 416:
query = 'SELECT ?item WHERE {\n ' .. kwargs.query .. '\n}'
query = 'SELECT ?item WHERE {\n ' .. kwargs.query .. '\n}'
end
end
local url = 'http://query.wikidata.org/#' .. frame:callParserFunction('urlencode', { query, 'PATH' })
local url = 'http://query.wikidata.org/#' .. mw.uri.encode(query, 'PATH')
local vizUrl = 'https://tools.wmflabs.org/hay/vizquery/#' .. frame:callParserFunction('urlencode', { query, 'PATH' })


if kwargs.description ~= nil then
if kwargs.description ~= nil then
return sprintf(messages['sparql_criterion_with_explanation'], {
return msg('criterion-sparql-with-explanation', kwargs.description, url)
description = kwargs.description,
queryLink = url,
vizQueryLink = vizUrl
})
end
end


return sprintf(messages['sparql_criterion'], {
return msg('criterion-sparql', url)
queryLink=url,
vizQueryLink=vizUrl
})
end

function criterion.stub(args, kwargs, frame)
-- deprecated
return messages['stub_criterion']
end
end


--- Formatter function for the templates criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.templates(args, kwargs, frame)
function criterion.templates(args, kwargs, frame)
local templates = makeTemplateList(frame, args)
return format_plural_criterion(make_template_list(args), 'templates')
return msg('criterion-templates', #templates, tostring(templates))
end
end


function criterion.format(frame)
--- Main function for getting criterion messages
-- @tparam table frame A frame object
local args, kwargs = parse_args(frame)
-- @treturn string A string representing the criterion (or an error message string)
local criterion_arg, args = shift_args(args)
function p.criterion(frame)
local args, kwargs = parseArgs(frame, 'criteria', true)
local criterionArg = table.remove(args, 1)
local permittedCriteria = getArgumentMapping('criteria', 'canonical')


if criterionArg == nil or criterionArg == '' then
-- Try to find the corresponding formatter or bail out if not found
return frame:preprocess(getErrorString('argument-missing', 'criterion'))
if criterion_arg == nil then
elseif permittedCriteria[criterionArg] == nil or criterion[criterionArg] == nil then
return frame:preprocess(make_error('argument_missing', 'criterion'))
return frame:preprocess(getErrorString('invalid-criterion', criterionArg))
end
local formatter = config.criteria[criterion_arg]
if formatter == nil or criterion[formatter] == nil then
return frame:preprocess(make_error('invalid_criterion', criterion_arg))
end
end


-- Use manual description if given
-- Use manual description if given
if kwargs.description ~= nil and formatter ~= 'sparql' then
if kwargs.description ~= nil and criterionArg ~= 'sparql' then
return kwargs.description
return kwargs.description
end
end


return frame:preprocess(criterion[criterionArg](args, kwargs, frame))
-- Generate auto-generated description
return frame:preprocess(criterion[formatter](args, kwargs, frame))
end
end


Linje 390: Linje 460:
local rule = {}
local rule = {}


--- Formatter function for custom rules
function rule.custom(points, args, kwargs)
-- @tparam number points A number of points; may by a float
return sprintf(messages['custom_rule'], {
-- @tparam table args Anonymous arguments to the module
['points'] = points,
-- @tparam table kwargs Keyword arguments to the module
['description'] = kwargs.description,
-- @treturn string A message corresponding to the rule
})
function rule.custom(points, args, kwargs, frame)
return msg('rule-custom', points, kwargs.description)
end
end


--- Formatter function for image rules
function rule.image(points, args, kwargs)
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.image(points, args, kwargs, frame)
local out
local out
local tplargs = {
local tplargs = {
Linje 403: Linje 480:
}
}
if kwargs.initialimagelimit ~= nil then
if kwargs.initialimagelimit ~= nil then
out = msg('rule-image-limited', points, kwargs.initialimagelimit)
out = messages['image_rule_limited']
tplargs['initialimagelimit'] = kwargs.initialimagelimit
else
else
out = messages['image_rule']
out = msg('rule-image', points)
end
end
if kwargs.ownimage ~= nil then
if kwargs.ownimage ~= nil then
out = out .. ' ' .. messages['image_rule_own']
out = out .. ' ' .. msg('rule-image-own', kwargs.ownimage)
tplargs['ownimage'] = kwargs.ownimage
end
end
return sprintf(out, tplargs)
return out
end
end


--- Formatter function for Wikidata rules
function rule.wikidata(points, args, kwargs)
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.wikidata(points, args, kwargs, frame)
local out
local out
local params
local params
local arg_types = { messages['properties'], messages['labels'], messages['aliases'], messages['descriptions'] }
local argTypes = { msg('properties'), msg('labels'), msg('aliases'), msg('descriptions') }
local results = {}
local results = {}
if kwargs.properties == nil and kwargs.labels == nil and kwargs.aliases == nil and kwargs.descriptions == nil then
if kwargs.properties == nil and kwargs.labels == nil and kwargs.aliases == nil and kwargs.descriptions == nil then
return make_error(
return getErrorString('argument-missing', listify(argTypes))
'argument_missing',
mw.text.listToText( arg_types, ', ', ' ' .. messages['or'] .. ' ' )
)
end
end
if kwargs.properties ~= nil then
if kwargs.properties ~= nil then
Linje 431: Linje 508:
params[k] = string.format('[[:d:Property:%s|%s]]', v, v)
params[k] = string.format('[[:d:Property:%s|%s]]', v, v)
end
end
table.insert(results, mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ))
table.insert(results, listify(params))
end
end
if kwargs.labels ~= nil then
if kwargs.labels ~= nil then
params = mw.text.split(kwargs.labels, ',')
params = mw.text.split(kwargs.labels, ',')
table.insert(results, messages['label'] .. ' (' .. mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ) .. ')')
table.insert(results, msg('label') .. ' (' .. listify(params) .. ')')
end
end
if kwargs.aliases ~= nil then
if kwargs.aliases ~= nil then
params = mw.text.split(kwargs.aliases, ',')
params = mw.text.split(kwargs.aliases, ',')
table.insert(results, messages['alias'] .. ' (' .. mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ) .. ')')
table.insert(results, msg('alias') .. ' (' .. listify(params) .. ')')
end
end
if kwargs.descriptions ~= nil then
if kwargs.descriptions ~= nil then
params = mw.text.split(kwargs.descriptions, ',')
params = mw.text.split(kwargs.descriptions, ',')
table.insert(results, messages['description'] .. ' (' .. mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ) .. ')')
table.insert(results, msg('description') .. ' (' .. listify(params) .. ')')
end
end
results = table.concat( results, ' ' .. messages['and'] .. ' ' )
results = table.concat( results, ' ' .. msg('and') .. ' ' )
if kwargs.all ~= nil then
if kwargs.all ~= nil then
out = messages['wikidata_rule_all']
out = msg('rule-wikidata-all', points, results)
else
else
out = messages['wikidata_rule_first']
out = msg('rule-wikidata-first', points, results)
end
end
if kwargs.require_reference ~= nil then
if kwargs.requireReference ~= nil then
out = out .. ' ' .. messages['wikidata_rule_require_reference']
out = out .. ' ' .. msg('rule-wikidata-require-reference')
end
end
return sprintf(out, {
return out
['points'] = points,
['thing'] = results,
})
end
end


--- Formatter function for reference rules
function rule.reference(points, args, kwargs)
-- @tparam number points A number of points; may by a float
return sprintf(messages['reference_rule'], {
-- @tparam table args Anonymous arguments to the module
['points'] = points,
-- @tparam table kwargs Keyword arguments to the module
['refpoints'] = args[1],
-- @treturn string A message corresponding to the rule
})
function rule.reference(points, args, kwargs, frame)
return msg('rule-reference', points, args[1])
end
end


--- Formatter function for template removal rules
function rule.templateremoval(points, args, kwargs)
-- @tparam number points A number of points; may by a float
local templates = format_plural(make_template_list(args), 'templates')
-- @tparam table args Anonymous arguments to the module
return sprintf(messages['templateremoval_rule'], {
-- @tparam table kwargs Keyword arguments to the module
['points'] = points,
-- @treturn string A message corresponding to the rule
['templates'] = templates,
function rule.templateremoval(points, args, kwargs, frame)
})
local templateList = makeTemplateList(frame, args)
return msg('rule-templateremoval', points, #templateList, tostring(templateList))
end
end


--- Formatter function for category removal rules
function rule.categoryremoval(points, args, kwargs)
-- @tparam number points A number of points; may by a float
local categories = format_plural(make_category_list(args), 'categories')
-- @tparam table args Anonymous arguments to the module
return sprintf(messages['categoryremoval_rule'], {
-- @tparam table kwargs Keyword arguments to the module
['points'] = points,
-- @treturn string A message corresponding to the rule
['categories'] = categories,
function rule.categoryremoval(points, args, kwargs, frame)
})
local categoryList = makeCategoryList(args)
return msg('rule-categoryremoval', points, #categoryList, tostring(categoryList))
end
end


function rule.section(points, args, kwargs)
--- Formatter function for section adding rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.section(points, args, kwargs, frame)
if kwargs.description ~= nil then
if kwargs.description ~= nil then
return msg('rule-section-desc', points, kwargs.description)
return sprintf(messages['section_rule_desc'], {
['points'] = points,
['description'] = kwargs.description,
})
end
end
return msg('rule-section', points, #args, listify(args))
local sections = format_plural(args, 'sections')
return sprintf(messages['section_rule'], {
['points'] = points,
['sections'] = sections,
})
end
end


--- Formatter function for byte bonus rules
function rule.bytebonus(points, args, kwargs)
-- @tparam number points A number of points; may by a float
return sprintf(messages['bytebonus_rule'], {
-- @tparam table args Anonymous arguments to the module
['points'] = points,
-- @tparam table kwargs Keyword arguments to the module
['bytes'] = args[1],
-- @treturn string A message corresponding to the rule
})
function rule.bytebonus(points, args, kwargs, frame)
return msg('rule-bytebonus', points, args[1])
end
end


--- Formatter function for word bonus rules
function rule.wordbonus(points, args, kwargs)
-- @tparam number points A number of points; may by a float
return sprintf(messages['wordbonus_rule'], {
-- @tparam table args Anonymous arguments to the module
['points'] = points,
-- @tparam table kwargs Keyword arguments to the module
['words'] = args[1],
-- @treturn string A message corresponding to the rule
})
function rule.wordbonus(points, args, kwargs, frame)
return msg('rule-wordbonus', points, args[1])
end
end


--- Main function for getting criterion messages
function rule.format(frame)
-- @tparam table frame A frame object
-- Make tables of anonymous and named arguments
-- @treturn string A string representing the rule (or an error message string)
local args, kwargs = parse_args(frame)
function p.rule(frame)
rule_arg, args = shift_args(args)
local args, kwargs = parseArgs(frame, 'rules', true)
points, args = shift_args(args)
local ruleArg = table.remove(args, 1)
local points = table.remove(args, 1)
local permittedRules = getArgumentMapping('rules', 'canonical')


if ruleArg == nil or ruleArg == '' then
-- Try to find the corresponding formatter or bail out if not found
return frame:preprocess(getErrorString('argument-missing', 'rule'))
if rule_arg == nil then
elseif permittedRules[ruleArg] == nil then
return frame:preprocess(make_error('argument_missing', 'rule'))
return frame:preprocess(getErrorString('invalid-rule', ruleArg))
end
local formatter = config.rules[rule_arg]
if formatter == nil then
return frame:preprocess(make_error('invalid_rule', rule_arg))
end
end

if kwargs.description ~= nil then
if kwargs.description ~= nil then
formatter = 'custom'
ruleArg = 'custom'
end
end


-- All rules requires argument 1: number of points awarded
-- All rules requires argument 1: number of points awarded
if points == nil then
if points == nil then
return frame:preprocess(make_error('argument_missing', '1 (number of points)'))
return frame:preprocess(getErrorString('argument-missing', '1 (number of points)'))
end
end


points = mw.language.getContentLanguage():formatNum(tonumber(points))
points = points:gsub( '%.', config['decimal_separator'])


-- If there's a rule formatter function, use it.
-- If there's a rule formatter function, use it.
-- Otherwise, use the string from the messages table.
-- Otherwise, use the string from the messages table.
local out
local out
if rule[formatter] ~= nil then
if rule[ruleArg] ~= nil then
out = rule[formatter](points, args, kwargs)
out = rule[ruleArg](points, args, kwargs, frame)
else
else
-- It shouldn't be necessary to check if the message exists here, because
out = sprintf(messages[formatter .. '_rule'], {
-- of the previous check against permittedRules above
['points'] = points,
out = msg('rule-' .. ruleArg, points)
})
end
end


if kwargs.site ~= nil then
if kwargs.site ~= nil then
out = msg('rule-site', out, mw.ustring.format('[https://%s %s]', kwargs.site, kwargs.site))
out = sprintf(messages['rule_site'], {
['baserule'] = out,
['site'] = string.format('[https://%s %s]', kwargs.site, kwargs.site),
})
end
end


if kwargs.max ~= nil then
if kwargs.max ~= nil then
out = msg('base-rule-max', out, mw.language.getContentLanguage():formatNum(tonumber(kwargs.max)))
out = sprintf(messages['base_rule_max'], {
['baserule'] = out,
['maxpoints'] = kwargs.max:gsub( '%.', config['decimal_separator']),
})
end
end


Linje 564: Linje 641:
end
end


--- Function to generate documentation for a module or template using this module
-- Export
-- Not implemented yet
return {
function p.generateDocs(frame)
['criterion'] = criterion.format,
-- Generate documentation subpage for templates using the module
['rule'] = rule.format,
end
}

--- Function to get warnings about duplicate or invalid i18n values
-- Not implemented yet
function p.getI18nWarnings(frame)
-- Function to be used on /doc page, to report any duplicate arguments
-- from the i18n, and potentially other things that should be fixed in the
-- i18n for the current language.
end

--- Get a single message string from the module's i18n, localized into the page
--- if possible
-- @tparam table frame A frame object
-- @treturn string A formatted message (or an HTML error string if the key doesn't exist)
function p.getMessage(frame)
local args, kwargs = parseArgs(frame, nil, false)
local key = table.remove(args, 1)
local exists, message = pcall(msg, key, args)
if exists then
if mw.isSubsting() then
-- substitute magic words etc. if the module proper is being substed
message = mw.ustring.gsub( message, '{{(#?%a+):', '{{subst:%1:' )
end
return frame:preprocess(message)
else
return getErrorString('message-key-missing', key)
end
end

--- Function to get i18n data for use by the bot
-- @treturn string A JSON-encoded string of all keys and (localized) values from the i18n dataset
function p.getAllI18n()
local lang = mw.title.getCurrentTitle().pageLang:getCode()
local sensible = {}
local i18n = mw.ext.data.get(I18NDATASET, lang)['data']
for _,v in ipairs(i18n) do
-- turn the array of message objects into a sensible key->value mapping
sensible[v[1]] = v[2]
end
return mw.text.jsonEncode(sensible)
end

return p

Siste sideversjon per 23. sep. 2025 kl. 07:38

Dokumentasjon for denne modulen kan opprettes på Modul:UKB/dok

require('strict')

local p = {}

local TNT = require('Module:TNT')
local I18NDATASET = 'I18n/UKB.tab'
local getArgs = require('Module:Arguments').getArgs

--- Get a localized message.
-- @param key The message key
-- @param ... Parameters to be passed to the message ($1, $2, etc.)
-- @return localized string
local function msg( key, ... )
	return TNT.format( I18NDATASET, key, ... )
end

--- Reverse a mapping to get a list of localized names => canonical names
-- @param mapping A table containing key-value pairs where the key is the canonical name and the value is an array table of aliases
-- @return A table of localized names => canonical names
local function mappingReverser(mapping)
	local ret = {}
	for canonical, synonyms in pairs(mapping) do
		for _, synonym in ipairs(synonyms) do
			ret[synonym] = canonical
		end
		local keyIsPresent, translations = pcall(msg, 'arg-' .. canonical)
		if keyIsPresent then
			translations = mw.text.split(translations, '|')
			for _, translation in ipairs(translations) do
				ret[translation] = canonical
			end
		end
	end
	return ret
end

--- Get the argument mapping for a type of item
-- @param itemType The mapping subtype to get. Either 'criteria' or 'rules'
-- @param returnType Which mapping to get; 'canonical' or 'translated'
-- @return A table of mappings
local function getArgumentMapping(itemType, returnType)
	-- if a new argument is added, it should also be added to the i18n module
	-- in [[c:Data:I18n/UKB.tab]]
	local argumentMapping = {
		['criteria'] = {
			['backlinks'] = { 'backlink' },
			['bytes'] = { 'byte' },
			['categories'] = { 'category' },
			['forwardlinks'] = { 'forwardlink' },
			['new'] = {},
			['existing'] = {},
			['namespaces'] = { 'namespace' },
			['pages'] = { 'page' },
			['sparql'] = {},
			['stub'] = {}, -- deprecated, not in i18n
			['templates'] = { 'template' }
		},
		['rules'] = {
			['bytes'] = { 'byte' },
			['bytebonus'] = {},
			['categoryremoval'] = {},
			['edit'] = {},
			['eligiblepage'] = {},
			['extlink'] = { 'exlink', 'externallink' },
			['image'] = { 'images' },
			['listbyte'] = { 'listbytes' },
			['newpage'] = {},
			['newredirect'] = {},
			['reference'] = { 'ref' },
			['section'] = {},
			['templateremoval'] = {},
			['wikidata'] = {},
			['word'] = { 'words' },
			['wordbonus'] = {}
		},
		['modifiers'] = {
			['aliases'] = {},
			['all'] = {},
			['description'] = {},
			['descriptions'] = {},
			['distinct'] = {},
			['ignore'] = {},
			['initialimagelimit'] = {},
			['labels'] = {},
			['max'] = {},
			['ownimage'] = {},
			['properties'] = {},
			['query'] = {},
			['requirereference'] = { 'require reference', 'require_reference' },
			['redirects'] = { 'redirect' },
			['site'] = {},
		}
	}

	if returnType == 'canonical' then
		return argumentMapping[itemType]
	end

	local translatedMap = {
		['criteria'] = mappingReverser(argumentMapping.criteria),
		['rules'] = mappingReverser(argumentMapping.rules),
		['modifiers'] = mappingReverser(argumentMapping.modifiers)
	}

	return translatedMap[itemType]
end

--[ Helper methods ] ------------------------------------------------------------------

--- Make an error string
-- @tparam string text Text to be wrapped in an error class
-- @treturn string The text wrapped in an error class
local function makeErrorString(text)
	local html = mw.html.create('strong')
		:addClass('error')
		:wikitext(text)
	return tostring(html)
end

--- Get an error string
-- @tparam string key A message key (from i18n)
-- @tparam string arg An argument to pass along to the message function
-- @treturn string An error message
local function getErrorString(key, arg)
	return makeErrorString(msg(key, arg))
end

--- Parse and translate anonymous and named arguments
-- @tparam table frame A frame object
-- @tparam string|nil itemType An item type to return ('criteria', 'rules' or nil)
-- @treturn table A table of anonymous arguments (args)
-- @treturn table A table of named arguments (kwargs)
local function parseArgs(frame, itemType, translate)
	local args = {}
	local kwargs = {}
	local canonicalMap = getArgumentMapping(itemType, 'translated')
	if itemType == nil then
		canonicalMap = {}
	end
	local kwargsMap = getArgumentMapping('modifiers', 'translated')
	for k, v in pairs(getArgs(frame)) do
		v = mw.text.trim(frame:preprocess(v))
		if v ~= '' then
			if type(k) == 'number' then
				if k == 1 and canonicalMap[v] ~= nil and translate then
					args[1] = canonicalMap[v]
				else
					args[k] = v
				end
			else
				if kwargsMap[k] ~= nil and translate then
					kwargs[kwargsMap[k]] = v
				else
					kwargs[k] = v
				end
			end
		end
	end
	return args, kwargs
end

--- Turn an array table into a string in list form
-- @tparam table items An array of items
-- @tparam string itemType Maybe unnecessary?
-- @tparam string word The strings 'or' or 'and' (representing i18n message keys)
-- @treturn string A string with the table returned as a list
local function listify(items, itemType, word)
	word = word or 'or'
	if #items == 0 then
		return getErrorString('anon-argument-missing', itemType)
	end
	if #items == 1 then
		return items[1]
	end
	return mw.text.listToText(items, ', ', ' ' .. msg(word) .. ' ' )
end

--- Get link data for a link to a page in a specific namespace
-- @tparam table frame A frame object
-- @tparam string ns A canonical (English) namespace name; 'Template' and 'Category' supported
-- @tparam string page A page name
-- @treturn table A table containing: language code, link target and page name
local function makeNsLink(frame, ns, page)
	local linkTarget
	local nsNumbers = {
		['Template'] = 10,
		['Category'] = 14
	}
	local lang, pageName = mw.ustring.match(page, '^([a-z]+):(.+)$') -- FIXME: Better language code detection
	if lang then
		-- English namespace name is guaranteed to work, avoids need to maintain
		-- lists of namespace names in the module
		linkTarget = mw.ustring.format(':%s:%s:%s', lang, ns, pageName)
	else
		linkTarget = mw.ustring.format(':%s:%s', frame:callParserFunction('ns', nsNumbers[ns]), page)
	end
	return {
		['lang'] = lang,
		['linkTarget'] = linkTarget,
		['pageName'] = pageName or page
	}
end

--- Make a link to a single template, wrapped in curly brace syntax
-- @tparam table frame A frame object
-- @tparam template Name of a template (optionally with an interlanguage prefix)
-- @treturn string An HTML string linking to the template in question
local function makeTemplateLink(frame, template)
	local nsLink = makeNsLink(frame, 'Template', template)
	local wikitext = mw.text.nowiki('{{') .. mw.ustring.format('[[%s|%s]]', nsLink['linkTarget'], nsLink['pageName']) .. mw.text.nowiki('}}')
	local html = mw.html.create('span')
		:addClass('template-link')
		:css('font-family', 'monospace,monospace')
		:wikitext(wikitext)
	return tostring(html)
end

--- Make a link to a single category
-- @tparam table frame A frame object
-- @tparam category Name of a category (optionally with an interlanguage prefix)
-- @treturn string An HTML string linking to the category in question
local function makeCategoryLink(frame, category)
	local nsLink = makeNsLink(frame, 'Category', category)
	return mw.ustring.format('[[%s|%s]]', nsLink['linkTarget'], nsLink['pageName'])
end

--- Make a list of templates
-- @tparam table frame A frame object
-- @tparam table args An array of template names (optionally with interlanguage prefixes)
-- @treturn table A table of template links
local function makeTemplateList(frame, args)
    local templates = {}
    for i, v in ipairs(args) do
    	table.insert(templates, makeTemplateLink(frame, v))
    end
    setmetatable(templates, {
    	__tostring = function(self)
    		return listify(templates, 'templates')
		end
    })
    return templates
end

--- Make a list of categories
-- @tparam table frame A frame object
-- @tparam table args An array of category names (optionally with interlanguage prefixes)
-- @treturn table A table of category links
local function makeCategoryList(frame, args)
    local categories = {}
    for i, v in ipairs(args) do
        v = mw.text.trim(v)
        if v ~= '' then
            table.insert(categories, makeCategoryLink(frame, v))
        end
    end
    setmetatable(categories, {
    	__tostring = function(self)
    		return listify(categories, 'categories')
		end
    })
    return categories
end

--- Make a list of templates
-- @tparam table args An array of page names (optionally with interlanguage prefixes)
-- @treturn table A table of page links
local function makePageList(args)
    local pages = {}
    for i, v in ipairs(args) do
        v = mw.text.trim(v)
        if v ~= '' then
            local lang, page = string.match(v, '^([a-z]+):(.+)$')
            if lang then
                table.insert(pages, string.format('[[:%s:%s|%s]]', lang, page, page))
            else
                table.insert(pages, string.format('[[:%s]]', v))
            end
        end
    end
    setmetatable(pages, {
    	__tostring = function(self)
    		return listify(pages, 'pages')
		end
    })
    return pages
end

--- Make a list of namespaces
-- @tparam table args An array of namespace IDs
-- @treturn table A table of namespace names
local function makeNsList(args)
    local namespaces = {}
    local namespaceName = msg('article')
    for _, namespaceId in ipairs(args) do
        namespaceId = mw.text.trim(namespaceId)
        if namespaceId ~= '' then
            if namespaceId ~= "0" then
                namespaceName = '{{lc:{{ns:' .. namespaceId .. '}}}}'
            end
            table.insert(namespaces, namespaceName)
        end
    end
    setmetatable(namespaces, {
    	__tostring = function(self)
    		return listify(namespaces, 'namespaces')
		end
    })
    return namespaces
end

--[ Criterion format methods ]-------------------------------------------------------------

local criterion = {}

--- Formatter function for the backlinks criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.backlinks(args, kwargs, frame)
	local pageList = makePageList(args)
	return msg('criterion-backlinks', #pageList, tostring(pageList))
end

--- Formatter function for the bytes criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.bytes(args, kwargs, frame)
	return msg('criterion-bytes', args[1])
end

--- Formatter function for the categories criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.categories(args, kwargs, frame)
	local categoryList = makeCategoryList(frame, args)
	local ret = msg('criterion-categories', #categoryList, tostring(categoryList))

	if kwargs.ignore ~= nil then
		local ignoredCats = mw.text.split(kwargs.ignore, ',')
		ignoredCats = makeCategoryList(frame, ignoredCats)
		ret = ret .. msg('categories-except', #ignoredCats, tostring(ignoredCats))
	end

    return ret
end

--- Formatter function for the existing criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.existing(args, kwargs, frame)
	return msg('criterion-existing')
end

--- Formatter function for the forwardlinks criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.forwardlinks(args, kwargs, frame)
	local pages = makePageList(args)
    return msg('criterion-forwardlinks', #pages, tostring(pages))
end

--- Formatter function for the namespaces criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.namespaces(args, kwargs, frame)
	local nsList = makeNsList(args)
	local message

	if #nsList == 1 and args[1] == '0' then
		message = msg('criterion-namespace-0')
	else
		message = msg('criterion-namespace', #nsList, tostring(nsList))
	end

	if kwargs.site ~= nil then
		return msg('page-at-site', message, mw.ustring.format('[https://%s %s]', kwargs.site, kwargs.site))
	end

	return message
end

--- Formatter function for the new page criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.new(args, kwargs, frame)
	if kwargs.redirects ~= nil then
		return msg('criterion-new-with-redirects')
	end
	return msg('criterion-new')
end

--- Formatter function for the pages (page list) criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.pages(args, kwargs, frame)
	local pages = makePageList(args)
	return msg('criterion-pages', #pages, tostring(pages))
end

--- Formatter function for the SPARQL criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.sparql(args, kwargs, frame)
	local query = ''
	if kwargs.distinct ~= nil then
		query = 'SELECT DISTINCT ?item WHERE {\n  ' .. kwargs.query .. '\n}'
	else
		query = 'SELECT ?item WHERE {\n  ' .. kwargs.query .. '\n}'
	end
    local url = 'http://query.wikidata.org/#' .. mw.uri.encode(query, 'PATH')

    if kwargs.description ~= nil then
        return msg('criterion-sparql-with-explanation', kwargs.description, url)
    end

    return msg('criterion-sparql', url)
end

--- Formatter function for the templates criterion
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the criterion
function criterion.templates(args, kwargs, frame)
	local templates = makeTemplateList(frame, args)
	return msg('criterion-templates', #templates, tostring(templates))
end

--- Main function for getting criterion messages
-- @tparam table frame A frame object
-- @treturn string A string representing the criterion (or an error message string)
function p.criterion(frame)
	local args, kwargs = parseArgs(frame, 'criteria', true)
	local criterionArg = table.remove(args, 1)
	local permittedCriteria = getArgumentMapping('criteria', 'canonical')

    if criterionArg == nil or criterionArg == '' then
        return frame:preprocess(getErrorString('argument-missing', 'criterion'))
    elseif permittedCriteria[criterionArg] == nil or criterion[criterionArg] == nil then
    	return frame:preprocess(getErrorString('invalid-criterion', criterionArg))
    end

    -- Use manual description if given
    if kwargs.description ~= nil and criterionArg ~= 'sparql' then
        return kwargs.description
    end

	return frame:preprocess(criterion[criterionArg](args, kwargs, frame))
end

--[ Rule format methods ]-------------------------------------------------------------

local rule = {}

--- Formatter function for custom rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.custom(points, args, kwargs, frame)
	return msg('rule-custom', points, kwargs.description)
end

--- Formatter function for image rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.image(points, args, kwargs, frame)
    local out
    local tplargs = {
        ['points'] = points,
    }
    if kwargs.initialimagelimit ~= nil then
    	out = msg('rule-image-limited', points, kwargs.initialimagelimit)
    else
        out = msg('rule-image', points)
    end
    if kwargs.ownimage ~= nil then
        out = out .. ' ' .. msg('rule-image-own', kwargs.ownimage)
    end
    return out
end

--- Formatter function for Wikidata rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.wikidata(points, args, kwargs, frame)
    local out
    local params
    local argTypes = { msg('properties'), msg('labels'), msg('aliases'), msg('descriptions') }
    local results = {}
    if kwargs.properties == nil and kwargs.labels == nil and kwargs.aliases == nil and kwargs.descriptions == nil then
        return getErrorString('argument-missing', listify(argTypes))
    end
    if kwargs.properties ~= nil then
        params = mw.text.split(kwargs.properties, ',')
        for k, v in pairs(params) do
            params[k] = string.format('[[:d:Property:%s|%s]]', v, v)
        end
        table.insert(results, listify(params))
    end
    if kwargs.labels ~= nil then
        params = mw.text.split(kwargs.labels, ',')
        table.insert(results, msg('label') .. ' (' .. listify(params) .. ')')
    end
    if kwargs.aliases ~= nil then
        params = mw.text.split(kwargs.aliases, ',')
        table.insert(results, msg('alias') .. ' (' .. listify(params) .. ')')
    end
    if kwargs.descriptions ~= nil then
        params = mw.text.split(kwargs.descriptions, ',')
        table.insert(results, msg('description') .. ' (' .. listify(params) .. ')')
    end
    results = table.concat( results, ' ' .. msg('and') .. ' ' )
    if kwargs.all ~= nil then
        out = msg('rule-wikidata-all', points, results)
    else
        out = msg('rule-wikidata-first', points, results)
    end
    if kwargs.requireReference ~= nil then
        out = out .. ' ' .. msg('rule-wikidata-require-reference')
    end
    return out
end

--- Formatter function for reference rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.reference(points, args, kwargs, frame)
	return msg('rule-reference', points, args[1])
end

--- Formatter function for template removal rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.templateremoval(points, args, kwargs, frame)
	local templateList = makeTemplateList(frame, args)
    return msg('rule-templateremoval', points, #templateList, tostring(templateList))
end

--- Formatter function for category removal rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.categoryremoval(points, args, kwargs, frame)
	local categoryList = makeCategoryList(args)
	return msg('rule-categoryremoval', points, #categoryList, tostring(categoryList))
end

--- Formatter function for section adding rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.section(points, args, kwargs, frame)
    if kwargs.description ~= nil then
    	return msg('rule-section-desc', points, kwargs.description)
    end
    return msg('rule-section', points, #args, listify(args))
end

--- Formatter function for byte bonus rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.bytebonus(points, args, kwargs, frame)
	return msg('rule-bytebonus', points, args[1])
end

--- Formatter function for word bonus rules
-- @tparam number points A number of points; may by a float
-- @tparam table args Anonymous arguments to the module
-- @tparam table kwargs Keyword arguments to the module
-- @treturn string A message corresponding to the rule
function rule.wordbonus(points, args, kwargs, frame)
	return msg('rule-wordbonus', points, args[1])
end

--- Main function for getting criterion messages
-- @tparam table frame A frame object
-- @treturn string A string representing the rule (or an error message string)
function p.rule(frame)
	local args, kwargs = parseArgs(frame, 'rules', true)
	local ruleArg = table.remove(args, 1)
	local points = table.remove(args, 1)
	local permittedRules = getArgumentMapping('rules', 'canonical')

    if ruleArg == nil or ruleArg == '' then
        return frame:preprocess(getErrorString('argument-missing', 'rule'))
    elseif permittedRules[ruleArg] == nil then
    	return frame:preprocess(getErrorString('invalid-rule', ruleArg))
    end

    if kwargs.description ~= nil then
        ruleArg = 'custom'
    end

    -- All rules requires argument 1: number of points awarded
    if points == nil then
        return frame:preprocess(getErrorString('argument-missing', '1 (number of points)'))
    end

	points = mw.language.getContentLanguage():formatNum(tonumber(points))

    -- If there's a rule formatter function, use it.
    -- Otherwise, use the string from the messages table.
    local out
    if rule[ruleArg] ~= nil then
        out = rule[ruleArg](points, args, kwargs, frame)
    else
    	-- It shouldn't be necessary to check if the message exists here, because
    	-- of the previous check against permittedRules above
        out = msg('rule-' .. ruleArg, points)
    end

    if kwargs.site ~= nil then
        out = msg('rule-site', out, mw.ustring.format('[https://%s %s]', kwargs.site, kwargs.site))
    end

    if kwargs.max ~= nil then
        out = msg('base-rule-max', out, mw.language.getContentLanguage():formatNum(tonumber(kwargs.max)))
    end

    return frame:preprocess(out)
end

--- Function to generate documentation for a module or template using this module
-- Not implemented yet
function p.generateDocs(frame)
	-- Generate documentation subpage for templates using the module
end

--- Function to get warnings about duplicate or invalid i18n values
-- Not implemented yet
function p.getI18nWarnings(frame)
	-- Function to be used on /doc page, to report any duplicate arguments
	-- from the i18n, and potentially other things that should be fixed in the
	-- i18n for the current language.
end

--- Get a single message string from the module's i18n, localized into the page
--- if possible
-- @tparam table frame A frame object
-- @treturn string A formatted message (or an HTML error string if the key doesn't exist)
function p.getMessage(frame)
	local args, kwargs = parseArgs(frame, nil, false)
	local key = table.remove(args, 1)
	local exists, message = pcall(msg, key, args)
	if exists then
		if mw.isSubsting() then
			-- substitute magic words etc. if the module proper is being substed
			message = mw.ustring.gsub( message, '{{(#?%a+):', '{{subst:%1:' )
		end
		return frame:preprocess(message)
	else
		return getErrorString('message-key-missing', key)
	end
end

--- Function to get i18n data for use by the bot
-- @treturn string A JSON-encoded string of all keys and (localized) values from the i18n dataset
function p.getAllI18n()
	local lang = mw.title.getCurrentTitle().pageLang:getCode()
	local sensible = {}
	local i18n = mw.ext.data.get(I18NDATASET, lang)['data']
	for _,v in ipairs(i18n) do
		-- turn the array of message objects into a sensible key->value mapping
		sensible[v[1]] = v[2]
	end
	return mw.text.jsonEncode(sensible)
end

return p