Module:Class: Difference between revisions

From Pantonian Republic Wiki
Jump to navigation Jump to search
(move templatestyles within td tag to help resolve some lint errors on some talk pages)
(No difference)

Revision as of 17:44, 19 November 2022

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

-- This module implements [[Template:Class]], [[Template:Class/icon]] and
-- [[Template:Class/colour]].

local mArguments -- lazily loaded
local definitions = mw.loadJsonData('Module:Class/definition.json')

local p = {}

--------------------------------------------------------------------------------
-- Local configuration and messages
--------------------------------------------------------------------------------

local cfg = {
	defaultCode = 'DEFAULT',
	classPrefix = 'assess-',
	globalClass = 'assess',
	defaultClassSuffix = 'default',
	unboldClassSuffix = 'unbold',
	catRootFormat = '%s %s',
	catTopicFormat = '%s %s articles',
	catBasicFormat = '%s articles',
	categoryFormat = '[[:Category:%s|%s]]',
	templateLocation = 'Template:Class',
	iconTemplateLocation = 'Template:Class/icon',
	colourTemplateLocation = 'Template:Class/colour',
	stylesLocation = 'Module:Class/styles.css',
	baseColourPath = {'colour', 'base'},
	iconPath = {"icon", "file"},
	iconDefaultPath = {"icon", "default"},
	iconAttribPath = {"icon", "requiresAttribution"},
	fullLabelPath = {"labels", "full"},
	shortLabelPath = {"labels", "short"},
	categoryRootPath = {"categoryRoot"},
	tooltipPath = {"labels", "tooltip"},
	yes = "yes",
	no = "no", 
	argumentNames = {
		class = "class",
		style = "style"
	},
	getOptions = {
		--First item is localized argument name, second is case-sensitivity
		bold = {"bold", false},
		header = {"header", false},
		image = {"image", false},
		rowspan = {"rowspan", false},
		fullcategory = {"fullcategory", true},
		category = {"category", true},
		topic = {"topic", true}
	}
}

--------------------------------------------------------------------------------
-- Argument helper functions
--------------------------------------------------------------------------------

local function getRawArgs(frame, wrapper)
	--Retrieves the arguments from the frame
	mArguments = mArguments or require('Module:Arguments')
	return mArguments.getArgs(frame, {
		wrappers = wrapper,
		trim = false,
		removeBlanks = false
	})
end

local function makeInvokeFunction(func, wrapper)
	--Wraps a general function into an invokable version
	return function (frame)
		local args = getRawArgs(frame, wrapper)
		return func(args)
	end
end

--------------------------------------------------------------------------------
-- String helper functions
--------------------------------------------------------------------------------

local function trim(str)
	--Trims strings, passes through non-strings without modification
	return (type(str) == 'string') and mw.text.trim(str) or str
end

local function normalizeValue(val)
	--Normalizes strings, particularly class codes
	if type(val) == 'string' then val = trim(val):lower() end
	if val == '' then val = nil end
	return val
end

local function ucfirst(str)
	--Capitalizes the first character of a string
	return mw.ustring.upper(mw.ustring.sub(str, 1, 1)) .. mw.ustring.sub(str, 2)
end

--------------------------------------------------------------------------------
-- Definition helper functions
--------------------------------------------------------------------------------

local function getDefinition(code)
	--Retrieves the definition and canonical class code for a given code.
	--Returns two values: the definition object and the canonical class code
	--string.
	local canonicalCode = normalizeValue(code)
	if code == cfg.defaultCode then canonicalCode = code end
	local class = definitions[canonicalCode]
	while class and class.alias do
		canonicalCode = class.alias
		class = definitions[class.alias]
	end
	if not class then
		return nil, nil
	end
	return class, canonicalCode
end

local function getDefault()
	--Shortcut function for retrieving the default definition
	return getDefinition(cfg.defaultCode) end

local function getProperty(class, default, map)
	--Retrieves a given property from a string given a class definition, a
	--default class definition, and a map for the path to traverse through the
	--class object. The map should be a sequential table of string property
	--names, e.g. {"colour", "base"} would retrieve someClass.colour.base
	local prop, dProp = class, default
	for k, v in ipairs(map) do
		prop = ((type(prop) == 'table') or nil) and prop[v]
		dProp = ((type(dProp) == 'table') or nil) and dProp[v]
	end
	if prop == nil then prop = dProp end
	return prop
end

--------------------------------------------------------------------------------
-- Color functions
--------------------------------------------------------------------------------

function p._colour(code)
	--Retrieves the base colour for a given code
	return getProperty(getDefinition(code), getDefault(), cfg.baseColourPath)
end

function p.colour(frame)
	--Retrieves the base colour for a given code; is invokable
	local args = getRawArgs(frame, cfg.colourTemplateLocation)
	-- Nowiki tags prevent output beginning with "#" from triggering bug 14974.
	return frame:extensionTag('nowiki', p._colour(args[1]))
end

--------------------------------------------------------------------------------
-- Icon functions
--------------------------------------------------------------------------------

function p._icon(args)
	--Retrieves an icon image and formats it as wikitext
	local class = getDefinition(args[cfg.argumentNames.class] or args[1])
	local default = getDefault()
	local file = getProperty(class, default, cfg.iconPath)
	local label = 
		getProperty(class, default, cfg.tooltipPath) or
		ucfirst(getProperty(class, default, cfg.fullLabelPath))
	local attrib = getProperty(class, default, cfg.iconAttribPath)
	local span = mw.html.create('span')

	span
		:cssText(args[cfg.argumentNames.style])
		:attr('title', label)
		:wikitext(
			string.format(
				'[[File:%s|%s|16px%s|class=noviewer|alt=]]',
				file,
				label,
				attrib and '' or '|link='
			)
		)
	return tostring(span)
end

p.icon = makeInvokeFunction(p._icon, cfg.iconTemplateLocation)
--Invokable version of p._icon

--------------------------------------------------------------------------------
-- Class functions
--------------------------------------------------------------------------------

function p._class(args)
	--Parses its arguments into a table cell with an optional icon, a name
	--linked to an appropriate category, and appropriate colour styling
	local classDef, classCode =
		getDefinition(args[cfg.argumentNames.class] or args[1])
	local default = getDefault()
	local iconDefault = getProperty(classDef, default, cfg.iconDefaultPath)
	local shortLabel = getProperty(classDef, default, cfg.shortLabelPath)
	local categoryRoot = getProperty(classDef, default, cfg.categoryRootPath)
	--o is short for "options", go for "get options". Bool true → case-sensitive
	local o, go = {}, cfg.getOptions
	for k, v in pairs(go) do
		o[k] = v[2] and trim(args[v[1]]) or normalizeValue(args[v[1]])
	end

	local cell = mw.html.create(o.header and 'th' or 'td')
	--image=yes forces icon, image=no disables it, otherwise checks default
	local icon = iconDefault and (o.image ~= cfg.no) or (o.image == cfg.yes)
	icon = icon and p.icon(args) .. ' ' or ''

	local category
	if o.fullcategory then
		category = o.fullcategory
	elseif o.category then
		category = string.format(cfg.catRootFormat, categoryRoot, o.category)
	elseif o.topic then
		category = string.format(cfg.catTopicFormat, categoryRoot, o.topic)
	else
		category = string.format(cfg.catBasicFormat, categoryRoot)
	end
	local text = string.format(cfg.categoryFormat, category, shortLabel)
	cell
		:addClass(cfg.globalClass)
		:addClass(
			o.bold == cfg.no and cfg.classPrefix .. cfg.unboldClassSuffix or nil
		)
		:addClass(cfg.classPrefix .. (classCode or cfg.defaultClassSuffix))
		:attr('rowspan', tonumber(o.rowspan))
		:wikitext(mw.getCurrentFrame():extensionTag{ name = 'templatestyles', args = {src = cfg.stylesLocation} }, icon, text)

	return tostring(cell)
end

p.class = makeInvokeFunction(p._class, cfg.templateLocation)
--Invokable version of p._class

return p