Changes

Pywikibot 6.4.0
local export = {}

-- TODO: a submodule for categorisation may be needed
-- these also need to be checked for existence by the documentation page

local m_common_data = require('Module:WikiProjectBanner/common data')

local
importance_grades,
quality_grades,
importance_scales,
quality_scales,
stock_notices
=
m_common_data.importance_grades,
m_common_data.quality_grades,
m_common_data.importance_scales,
m_common_data.quality_scales,
m_common_data.stock_notices

-- creates a wrapper object which tracks unused template arguments
local function track_usage(args)
local tracker = {}

for key in pairs(args) do
tracker[key] = true
end

return setmetatable({}, {
__index = function (self, key)
local value = args[key]
tracker[key] = nil
self[key] = value
return value
end
}), tracker
end

-- resolves a quality or importance assessment grade; returns a grade data table and status
local function resolve_grade(scale_config, scales, grades, args, grade_param, title)
if args == true then
return grades.na, 'demo'
end

if type(scale_config) == "string" then
scale_config = scales[scale_config]
elseif not scale_config then
scale_config = scales.standard
end

local ns
ns = mw.site.namespaces[title.namespace].subject
if ns.id == 0 then
ns = "_MAIN"
else
ns = ns.canonicalName
end

if title.isRedirect and scale_config._REDIRECT then
local redir_grade = scale_config._REDIRECT[ns] or scale_config._REDIRECT._OTHER
if redir_grade then
return grades[redir_grade], 'redirect'
end
end

scale_config = scale_config[ns] or scale_config._OTHER
if not scale_config then
return nil
end

if type(scale_config) == "string" then
return grades[scale_config]
end

local resolver = {}
for _, item in ipairs(scale_config) do
local grade = grades[item]
resolver[item] = grade
if grade.aliases then
for _, item in ipairs(grade.aliases) do
resolver[item] = grade
end
end
end

local grade = args[grade_param]
if grade then
grade = tostring(grade):lower()
if resolver[grade] then
return resolver[grade], 'valid'
else
return resolver[scale_config[1]], 'invalid'
end
else
return resolver[scale_config[1]], 'default'
end
end

-- constructs banner markup and the category list. for internal use only (which includes unit tests).
function export.build_banner(banner_config, banner_hooks, title, banner_args, out, categories)
local yesno = require('Module:yesno')

out.root = mw.html.create('')
local state = {} -- for use by hooks only

local function call_hook(hookfunc, ...)
if not hookfunc then
return true
end
return hookfunc(--[[ not yet determined ]])
end

-- basic skeleton
out.wrapper =
out.root:tag('table')
:addClass('tmbox tmbox-notice collapsible innercollapse wpb')

out.header_row =
out.wrapper:tag('tr')
:addClass('wpb-header')
out.header_name =
out.header_row:tag('td')
:css('text-align', 'right')
:css('padding', '0.3em 1em 0.3em 0.3em')
:css('width', '50%')
:css('font-weight', 'bold')
out.header_rating =
out.header_row:tag('th')
:css('text-align', 'left')
:css('width', '50%')
:css('padding', '0.3em 0.3em 0.3em 0')

out.content =
out.wrapper:tag('tr')
:tag('td')
:addClass('mbox-text')
:css('padding', '3px 0 3px 5px')
:attr('colspan', '2')
:tag('table')
:css('background', 'transparent')
:css('border', 'none')
:css('padding', '0')
:css('width', '100%')
:attr('cellspacing', '0')

out.has_more = false
out.content_more =
mw.html.create('table')
:addClass('collapsible collapsed')
:css('width', '100%')
:css('background', 'transparent')
:tag('tr')
:tag('th')
:attr('colspan', '3')
:css('text-align', 'left')
:css('padding', '0.2em 2px 0.2em 0')
:wikitext(banner_config.more_header or "More information")
:done()
:done()

-- does anyone still use this?
local is_small = (banner_args ~= true) and yesno(banner_args.small)
if is_small then
out.wrapper:addClass("mbox-small")
end

-- the blurb
local page_type = require('Module:pagetype')._main { page = title.fullText }

local blurb_row = out.content:tag('tr')
if banner_config.image_left then
out.blurb_image_left = blurb_row:tag('td'):addClass('mbox-image')
end
out.blurb_text = blurb_row:tag('td'):addClass('mbox-text')
if banner_config.image_right then
out.blurb_image_right = blurb_row:tag('td'):addClass('mbox-imageright')
end

if banner_config.image_left then
out.blurb_image_left:wikitext(('[[File:%s|%s]]'):format(
banner_config.image_left,
is_small
and (banner_config.image_left_size_small or "40px")
or (banner_config.image_left_size_big or "80px")
))
end

if banner_config.image_right then
out.blurb_image_right:wikitext(('[[File:%s|%s]]'):format(
banner_config.image_right,
is_small
and (banner_config.image_right_size_small or "40px")
or (banner_config.image_right_size_big or "80px")
))
end

out.blurb_text:attr('colspan',
(banner_config.image_left and 1 or 0) +
(banner_config.image_right and 1 or 0) +
1
)

if banner_config.portal then
local m_portal = require("Module:Portal")
out.blurb_text:wikitext(m_portal._portal({ banner_config.portal }, {}))
end

local project_link = banner_config.project_link or ("Wikipedia:WikiProject " .. banner_config.project)
local project_name = banner_config.project_name or ("WikiProject " .. banner_config.project)
if banner_config.blurb then
out.blurb_text:wikitext(banner_config.blurb)
else
local project_scope = banner_config.project_scope or ("[[" .. banner_config.project .. "]]")
local project_link_talk = project_link:gsub("^Wikipedia:", "Wikipedia talk:") -- XXX: avoiding title objects because they are "expensive" to create

out.blurb_text:wikitext((
"This %s is within the scope of '''[[%s|%s]]''', a collaborative effort " ..
"to improve the coverage of %s on Wikipedia. If you would like to participate, " ..
"please visit the project page, where you can join the [[%s|discussion]] and " ..
"see a list of open tasks."
):format(
page_type, project_link, project_name,
project_scope, project_link_talk
))
end

out.header_name:wikitext(("[[%s|%s]]"):format(project_link, project_name))

function out.row_pair(in_more)
local parent = out.content
if in_more then
out.has_more = true
parent = out.content_more
end
local row = parent:tag('tr')
local cell_img, cell_text

cell_img = row:tag('td')
cell_text = row:tag('td')
:attr('colspan', '2')
:addClass('mbox-text')

return cell_img, cell_text
end

-- normalise parameters
local quality_grade, quality_grade_status = resolve_grade(
banner_config.quality_scale,
quality_scales,
quality_grades,
banner_args, 'class',
title
)

if quality_grade_status == 'invalid' then
-- TODO: add a category
end

local imp_grade, imp_grade_status
if quality_grade and quality_grade.force_imp then
imp_grade, imp_grade_status = quality_grade.force_imp, 'forced'
else
imp_grade, imp_grade_status = resolve_grade(
banner_config.importance_scale,
importance_scales,
importance_grades,
banner_args, banner_config.importance_param or 'importance',
title
)
end

if imp_grade_status == 'invalid' then
-- TODO: add a category
end

if quality_grade then
out.qual_label, out.qual_text = out.row_pair()
out.qual_label
:addClass('assess')
:css('text-align', 'center')
:css('white-space', 'nowrap')
:css('font-weight', 'bold')
:css('background', quality_grade.color)

if quality_grade.icon then
out.qual_label
:wikitext('[[File:' .. quality_grade.icon .. '|16px]] ')
end
out.qual_label:wikitext(quality_grade.short)
out.qual_text:wikitext(("This %s %s on the project's [[%s|quality scale]]."):format(
page_type, quality_grade.rated_text or
"has been rated as '''" .. quality_grade.full .. "'''",
banner_config.quality_scale_link or
(project_link .. "/Assessment#Quality scale")
))

-- TODO: add a category
-- TODO: assessment checklists
end

if imp_grade then
out.imp_label, out.imp_text = out.row_pair()
out.imp_label
:addClass('import')
:css('text-align', 'center')
:css('white-space', 'nowrap')
:css('font-weight', 'bold')
:css('background', imp_grade.color)
out.imp_label:wikitext(imp_grade.name)
out.imp_text:wikitext(("This %s %s on the project's [[%s|importance scale]]."):format(
page_type, quality_grade.rated_text or
"has been rated as '''" .. imp_grade.name .. "-importance'''",
banner_config.importance_scale_link or
(project_link .. "/Assessment#Importance scale")
))

-- TODO: add a category
end

-- rating text for banner headers inside {{WikiProjectBannerShell}}
if quality_grade or imp_grade then
out.header_rating:wikitext("(Rated ")
if quality_grade then
out.header_rating:wikitext(quality_grade.short)
if imp_grade then
out.header_rating:wikitext(", ")
end
end
if imp_grade then
out.header_rating:wikitext(imp_grade.name .. "-importance")
end
out.header_rating:wikitext(")")
end

-- field, like in {{WikiProject Systems}} or {{Maths rating}}
if banner_config.field then
local field_config = banner_config.field
local field

if banner_args ~= true then
field = false
local field_id = banner_args[field_config.arg_name or 'field']

if field_id then
field = field_config.fields[field_id]
if type(field) == 'string' then
field = field_config.fields[field]
end
end
end

out.field_icon, out.field_text = out.row_pair()
if field then
out.field_icon:wikitext(("[[File:%s|link=%s]]"):format(
field.icon, field.link
))
out.field_text:wikitext(("This %s is within the field of [[%s|%s]]."):format(
page_type, field.link, field.name
))
else
out.field_icon:wikitext(("[[File:Purple question mark.svg|link=%s]]"):format(
field_config.unassessed_link
))
out.field_text:wikitext(("This %s is [[%s|not associated with a particular field]]."):format(
page_type, field_config.unassessed_link
))

if field == false then
-- TODO: add a category because an invalid field has been specified
end
end
end

-- task forces
for _, tf_info in ipairs(banner_config.task_forces or {}) do
-- TODO: is this row needed?
local needed = (banner_args == true) or banner_args[tf_info.param]
if tf_info.force then -- {{WikiProject Software}} forces WikiProject Computing's banner for example
needed = true
end

if needed then
local short_name = tf_info.short_name or tf_info.name
if tf_info.link then
out.header_name:wikitext((" / [[%s|%s]]"):format(tf_info.link, short_name))
else
out.header_name:wikitext((" / %s"):format(short_name))
end

local node_icon, node_text = out.row_pair()

if tf_info.icon then
node_icon:wikitext(('[[File:%s|x25px|link=%s]]'):format(tf_info.icon, tf_info.link or ''))
end

-- TODO: node_text:wikitext("This %s is supported by %s.")
-- TODO: add categories
end
end

-- requests and other notices (photograph, maps, attention, etc.)
for _, nt_info in ipairs(banner_config.notices or { stock_notices.auto, stock_notices.attention }) do
if type(nt_info) == 'string' then
nt_info = stock_notices[nt_info] or
error("Invalid stock notice '" .. nt_info .. "' specified in banner configuration")
end

local needed = (banner_args == true) or banner_args[nt_info.param]

if needed then
local node_icon, node_text = out.row_pair()

if nt_info.icon then
node_icon:wikitext(('[[File:%s|x25px|link=%s]]'):format(nt_info.icon, nt_info.link or ''))
end

-- TODO: fill node_text
-- TODO: add categories
end
end

if out.has_more then
out.content:wikitext(tostring(out.content_more))
end
end

function export.render_banner(frame)
local banner_name, is_templ = frame:getParent():getTitle():gsub("^Template:", "")
banner_name = banner_name:match("^(.*)/sandbox$") or banner_name
if is_templ == 0 then
error("This module must be invoked from within a template")
end

local demo = false
if mw.isSubsting() then
local result = { }
for key, value in pairs(frame:getParent().args) do
table.insert(result, "|" .. key .. "=" .. value)
end

return "{{" .. banner_name .. table.concat(result) .. "}}"
elseif mw.title.getCurrentTitle().fullText == frame:getParent():getTitle() then
demo = true
elseif mw.title.getCurrentTitle().namespace == mw.site.namespaces.Module.id then
-- we are viewing it on the banner config page (?)
demo = true
end

local banner_args, unused_args
if not demo then
banner_args, unused_args = track_usage(frame:getParent().args)
else
banner_args, unused_args = frame:getParent().args, {}
end

local success, banner_config, banner_data

success, banner_config = pcall(mw.loadData, "Module:WikiProjectBanner/config/" .. banner_name)
if not success then
error("Banner data page [[Module:WikiProjectBanner/config/" .. banner_name .. "]] does not exist")
end

success, banner_hooks = pcall(require, "Module:WikiProjectBanner/config/" .. banner_name .. "/hooks") or {}
if not success then
banner_hooks = {}
end

local out, categories = {}, {}

export.build_banner(
banner_config, banner_hooks, -- banner config
mw.title.getCurrentTitle(), demo or banner_args, -- current environment
out, categories -- output
)

if next(unused_args) then
-- TODO: output a category for unused arguments
end

-- categories
local sort_key = banner_args.listas or mw.title.getCurrentTitle().text
if (#categories > 0) and yesno(banner_args.category, true) then
categories = "[[Category:" .. table.concat(categories, "|" .. sort_key .. "]][[Category:") .. "|" .. sort_key .. "]]"
else
categories = ""
end

return tostring(out.root) .. categories
end

return export