Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.
Created page with "-------------------------------------------------------------------------------- -- Module:Hatnote list -- -- -- -- This module produces and formats lists for use in hatnotes. In particular, -- -- it implements the for-see list, i.e. lists of "For X, see Y" statements, -- -- as used in {{about}}, {{redirect}}, and their variants. Also introd..."
 
No edit summary
Line 1: Line 1:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--                           Module:Hatnote list                              --
--                             Module:Hatnote                               --
--                                                                            --
--                                                                            --
-- This module produces and formats lists for use in hatnotes. In particular, --
-- This module produces hatnote links and links to related articles. It      --
-- it implements the for-see list, i.e. lists of "For X, see Y" statements,  --
-- implements the {{hatnote}} and {{format link}} meta-templates and includes --
-- as used in {{about}}, {{redirect}}, and their variants. Also introduced    --
-- helper functions for other Lua hatnote modules.                           --
-- are andList & orList helpers for formatting lists with those conjunctions. --
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


local mArguments --initialize lazily
local mFormatLink = require('Module:Format link')
local mHatnote = require('Module:Hatnote')
local libraryUtil = require('libraryUtil')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkType = libraryUtil.checkType
local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg
local mArguments -- lazily initialise [[Module:Arguments]]
local yesno -- lazily initialise [[Module:Yesno]]
local formatLink -- lazily initialise [[Module:Format link]] ._formatLink
local p = {}
local p = {}


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- List stringification helper functions
-- Helper functions
--
-- These functions are used for stringifying lists, usually page lists inside
-- the "Y" portion of "For X, see Y" for-see items.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


--default options table used across the list stringification functions
local function getArgs(frame)
local stringifyListDefaultOptions = {
-- Fetches the arguments from the parent frame. Whitespace is trimmed and
conjunction = "and",
-- blanks are removed.
separator = ",",
mArguments = require('Module:Arguments')
altSeparator = ";",
return mArguments.getArgs(frame, {parentOnly = true})
space = " ",
end
formatted = false
}


--Searches display text only
local function removeInitialColon(s)
local function searchDisp(haystack, needle)
-- Removes the initial colon from a string, if present.
return string.find(
return s:match('^:?(.*)')
string.sub(haystack, (string.find(haystack, '|') or 0) + 1), needle
end
)
 
function p.defaultClasses(inline)
-- Provides the default hatnote classes as a space-separated string; useful
-- for hatnote-manipulation modules like [[Module:Hatnote group]].
return
(inline == 1 and 'hatnote-inline' or 'hatnote') .. ' ' ..
'navigation-not-searchable'
end
 
function p.disambiguate(page, disambiguator)
-- Formats a page title with a disambiguation parenthetical,
-- i.e. "Example" ? "Example (disambiguation)".
checkType('disambiguate', 1, page, 'string')
checkType('disambiguate', 2, disambiguator, 'string', true)
disambiguator = disambiguator or 'disambiguation'
return mw.ustring.format('%s (%s)', page, disambiguator)
end
end


-- Stringifies a list generically; probably shouldn't be used directly
function p.findNamespaceId(link, removeColon)
local function stringifyList(list, options)
-- Finds the namespace id (namespace number) of a link or a pagename. This
-- Type-checks, defaults, and a shortcut
-- function will not work if the link is enclosed in double brackets. Colons
checkType("stringifyList", 1, list, "table")
-- are trimmed from the start of the link by default. To skip colon
if #list == 0 then return nil end
-- trimming, set the removeColon parameter to false.
checkType("stringifyList", 2, options, "table", true)
checkType('findNamespaceId', 1, link, 'string')
options = options or {}
checkType('findNamespaceId', 2, removeColon, 'boolean', true)
for k, v in pairs(stringifyListDefaultOptions) do
if removeColon ~= false then
if options[k] == nil then options[k] = v end
link = removeInitialColon(link)
end
end
local s = options.space
local namespace = link:match('^(.-):')
-- Format the list if requested
if namespace then
if options.formatted then
local nsTable = mw.site.namespaces[namespace]
list = mFormatLink.formatPages(
if nsTable then
{categorizeMissing = mHatnote.missingTargetCat}, list
return nsTable.id
)
end
end
end
-- Set the separator; if any item contains it, use the alternate separator
return 0
local separator = options.separator
end
for k, v in pairs(list) do
 
if searchDisp(v, separator) then
function p.makeWikitextError(msg, helpLink, addTrackingCategory, title)
separator = options.altSeparator
-- Formats an error message to be returned to wikitext. If
break
-- addTrackingCategory is not false after being returned from
end
-- [[Module:Yesno]], and if we are not on a talk page, a tracking category
-- is added.
checkType('makeWikitextError', 1, msg, 'string')
checkType('makeWikitextError', 2, helpLink, 'string', true)
yesno = require('Module:Yesno')
title = title or mw.title.getCurrentTitle()
-- Make the help link text.
local helpText
if helpLink then
helpText = ' ([[' .. helpLink .. '|help]])'
else
helpText = ''
end
end
-- Set the conjunction, apply Oxford comma, and force a comma if #1 has " "
-- Make the category text.
local conjunction = s .. options.conjunction .. s
local category
if #list == 2 and searchDisp(list[1], " ") or #list > 2 then
if not title.isTalkPage -- Don't categorise talk pages
conjunction = separator .. conjunction
and title.namespace ~= 2 -- Don't categorise userspace
and yesno(addTrackingCategory) ~= false -- Allow opting out
then
category = 'Hatnote templates with errors'
category = mw.ustring.format(
'[[%s:%s]]',
mw.site.namespaces[14].name,
category
)
else
category = ''
end
end
-- Return the formatted string
return mw.ustring.format(
return mw.text.listToText(list, separator .. s, conjunction)
'<strong class="error">Error: %s%s.</strong>%s',
msg,
helpText,
category
)
end
end


--DRY function
local curNs = mw.title.getCurrentTitle().namespace
function p.conjList (conj, list, fmt)
p.missingTargetCat =
return stringifyList(list, {conjunction = conj, formatted = fmt})
--Default missing target category, exported for use in related modules
((curNs ==  0) or (curNs == 14)) and
'Articles with hatnote templates targeting a nonexistent page' or nil
 
function p.quote(title)
--Wraps titles in quotation marks. If the title starts/ends with a quotation
--mark, kerns that side as with {{-'}}
local quotationMarks = {
["'"]=true, ['"']=true, [' ']=true, [" "]=true, [' ']=true, [" "]=true
}
local quoteLeft, quoteRight = -- Test if start/end are quotation marks
quotationMarks[string.sub(title,  1,  1)],
quotationMarks[string.sub(title, -1, -1)]
if quoteLeft or quoteRight then
title = mw.html.create("span"):wikitext(title)
end
if quoteLeft  then title:css("padding-left",  "0.15em") end
if quoteRight then title:css("padding-right", "0.15em") end
return '"' .. tostring(title) .. '"'
end
end
-- Stringifies lists with "and" or "or"
function p.andList (...) return p.conjList("and", ...) end
function p.orList (...) return p.conjList("or", ...) end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- For see
-- Hatnote
--
--
-- Makes a "For X, see [[Y]]." list from raw parameters. Intended for the
-- Produces standard hatnote text. Implements the {{hatnote}} template.
-- {{about}} and {{redirect}} templates and their variants.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


--default options table used across the forSee family of functions
function p.hatnote(frame)
local forSeeDefaultOptions = {
local args = getArgs(frame)
andKeyword = 'and',
local s = args[1]
title = mw.title.getCurrentTitle().text,
if not s then
otherText = 'other uses',
return p.makeWikitextError(
forSeeForm = 'For %s, see %s.',
'no text specified',
}
'Template:Hatnote#Errors',
 
args.category
--Collapses duplicate punctuation
)
local function punctuationCollapse (text)
local replacements = {
["%.%.$"] = ".",
["%?%.$"] = "?",
["%!%.$"] = "!",
["%.%]%]%.$"] = ".]]",
["%?%]%]%.$"] = "?]]",
["%!%]%]%.$"] = "!]]"
}
for k, v in pairs(replacements) do text = string.gsub(text, k, v) end
return text
end
 
-- Structures arguments into a table for stringification, & options
function p.forSeeArgsToTable (args, from, options)
-- Type-checks and defaults
checkType("forSeeArgsToTable", 1, args, 'table')
checkType("forSeeArgsToTable", 2, from, 'number', true)
from = from or 1
checkType("forSeeArgsToTable", 3, options, 'table', true)
options = options or {}
for k, v in pairs(forSeeDefaultOptions) do
if options[k] == nil then options[k] = v end
end
-- maxArg's gotten manually because getArgs() and table.maxn aren't friends
local maxArg = 0
for k, v in pairs(args) do
if type(k) == 'number' and k > maxArg then maxArg = k end
end
end
-- Structure the data out from the parameter list:
return p._hatnote(s, {
-- * forTable is the wrapper table, with forRow rows
extraclasses = args.extraclasses,
-- * Rows are tables of a "use" string & a "pages" table of pagename strings
selfref = args.selfref
-- * Blanks are left empty for defaulting elsewhere, but can terminate list
})
local forTable = {}
local i = from
local terminated = false
-- If there is extra text, and no arguments are given, give nil value
-- to not produce default of "For other uses, see foo (disambiguation)"
if options.extratext and i > maxArg then return nil end
-- Loop to generate rows
repeat
-- New empty row
local forRow = {}
-- On blank use, assume list's ended & break at end of this loop
forRow.use = args[i]
if not args[i] then terminated = true end
-- New empty list of pages
forRow.pages = {}
-- Insert first pages item if present
table.insert(forRow.pages, args[i + 1])
-- If the param after next is "and", do inner loop to collect params
-- until the "and"'s stop. Blanks are ignored: "1|and||and|3" ? {1, 3}
while args[i + 2] == options.andKeyword do
if args[i + 3] then
table.insert(forRow.pages, args[i + 3])
end
-- Increment to next "and"
i = i + 2
end
-- Increment to next use
i = i + 2
-- Append the row
table.insert(forTable, forRow)
until terminated or i > maxArg
return forTable
end
end


-- Stringifies a table as formatted by forSeeArgsToTable
function p._hatnote(s, options)
function p.forSeeTableToString (forSeeTable, options)
checkType('_hatnote', 1, s, 'string')
-- Type-checks and defaults
checkType('_hatnote', 2, options, 'table', true)
checkType("forSeeTableToString", 1, forSeeTable, "table", true)
checkType("forSeeTableToString", 2, options, "table", true)
options = options or {}
options = options or {}
for k, v in pairs(forSeeDefaultOptions) do
local inline = options.inline
if options[k] == nil then options[k] = v end
local hatnote = mw.html.create(inline == 1 and 'span' or 'div')
end
local extraclasses
-- Stringify each for-see item into a list
if type(options.extraclasses) == 'string' then
local strList = {}
extraclasses = options.extraclasses
if forSeeTable then
for k, v in pairs(forSeeTable) do
local useStr = v.use or options.otherText
local pagesStr =
p.andList(v.pages, true) or
mFormatLink._formatLink{
categorizeMissing = mHatnote.missingTargetCat,
link = mHatnote.disambiguate(options.title)
}
local forSeeStr = string.format(options.forSeeForm, useStr, pagesStr)
forSeeStr = punctuationCollapse(forSeeStr)
table.insert(strList, forSeeStr)
end
end
end
if options.extratext then table.insert(strList, punctuationCollapse(options.extratext..'.')) end
-- Return the concatenated list
return table.concat(strList, ' ')
end


-- Produces a "For X, see [[Y]]" string from arguments. Expects index gaps
hatnote
-- but not blank/whitespace values. Ignores named args and args < "from".
:attr('role', 'note')
function p._forSee (args, from, options)
:addClass(p.defaultClasses(inline))
local forSeeTable = p.forSeeArgsToTable(args, from, options)
:addClass(extraclasses)
return p.forSeeTableToString(forSeeTable, options)
:addClass(options.selfref and 'selfref' or nil)
end
:wikitext(s)


-- As _forSee, but uses the frame.
return mw.getCurrentFrame():extensionTag{
function p.forSee (frame, from, options)
name = 'templatestyles', args = { src = 'Module:Hatnote/styles.css' }
mArguments = require('Module:Arguments')
} .. tostring(hatnote)
return p._forSee(mArguments.getArgs(frame), from, options)
end
end


return p
return p

Revision as of 03:23, 14 February 2025

This is a meta-module that provides various functions for making hatnotes. It implements the {{hatnote}} template, for use in hatnotes at the top of pages. It also contains a number of helper functions for use in other Lua hatnote modules.

Use from wikitext

The functions in this module cannot be used directly from #invoke, and must be used through templates instead. Please see Template:Hatnote and Template:Format link for documentation.

Use from other Lua modules

To load this module from another Lua module, use the following code.

local mHatnote = require('Module:Hatnote')

You can then use the functions as documented below.

Hatnote

mHatnote._hatnote(s, options)

Formats the string s as a hatnote. This encloses s in the tags <div class="hatnote">...</div>. Options are provided in the options table. Options include:

  • options.extraclasses - a string of extra classes to provide
  • options.selfref - if this is not nil or false, adds the class "selfref", used to denote self-references to Wikipedia (see Template:Self-reference))

The CSS of the hatnote class is defined in Module:Hatnote/styles.css.

Example 1
mHatnote._hatnote('This is a hatnote.')

Produces: <div class="hatnote">This is a hatnote.</div>

Displays as:

Example 2
mHatnote._hatnote('This is a hatnote.', {extraclasses = 'boilerplate seealso', selfref = true})

Produces: <div class="hatnote boilerplate seealso selfref">This is a hatnote.</div>

Displayed as:

Find namespace id

mHatnote.findNamespaceId(link, removeColon)

Finds the namespace id of the string link, which should be a valid page name, with or without the section name. This function will not work if the page name is enclosed with square brackets. When trying to parse the namespace name, colons are removed from the start of the link by default. This is helpful if users have specified colons when they are not strictly necessary. If you do not need to check for initial colons, set removeColon to false.

Examples
mHatnote.findNamespaceId('Lion') → 0
mHatnote.findNamespaceId('Category:Lions') → 14
mHatnote.findNamespaceId(':Category:Lions') → 14
mHatnote.findNamespaceId(':Category:Lions', false) → 0 (the namespace is detected as ":Category", rather than "Category")

Make wikitext error

mHatnote.makeWikitextError(msg, helpLink, addTrackingCategory)

Formats the string msg as a red wikitext error message, with optional link to a help page helpLink. Normally this function also adds Category:Hatnote templates with errors (0). To suppress categorization, pass false as third parameter of the function (addTrackingCategory).

Examples:

mHatnote.makeWikitextError('an error has occurred')Error: an error has occurred.
mHatnote.makeWikitextError('an error has occurred', 'Template:Example#Errors')Error: an error has occurred (help).

Examples

For an example of how this module is used in other Lua modules, see Module:Labelled list hatnote


--------------------------------------------------------------------------------
--                              Module:Hatnote                                --
--                                                                            --
-- This module produces hatnote links and links to related articles. It       --
-- implements the {{hatnote}} and {{format link}} meta-templates and includes --
-- helper functions for other Lua hatnote modules.                            --
--------------------------------------------------------------------------------

local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg
local mArguments -- lazily initialise [[Module:Arguments]]
local yesno -- lazily initialise [[Module:Yesno]]
local formatLink -- lazily initialise [[Module:Format link]] ._formatLink

local p = {}

--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------

local function getArgs(frame)
	-- Fetches the arguments from the parent frame. Whitespace is trimmed and
	-- blanks are removed.
	mArguments = require('Module:Arguments')
	return mArguments.getArgs(frame, {parentOnly = true})
end

local function removeInitialColon(s)
	-- Removes the initial colon from a string, if present.
	return s:match('^:?(.*)')
end

function p.defaultClasses(inline)
	-- Provides the default hatnote classes as a space-separated string; useful
	-- for hatnote-manipulation modules like [[Module:Hatnote group]].
	return
		(inline == 1 and 'hatnote-inline' or 'hatnote') .. ' ' ..
		'navigation-not-searchable'
end

function p.disambiguate(page, disambiguator)
	-- Formats a page title with a disambiguation parenthetical,
	-- i.e. "Example" ? "Example (disambiguation)".
	checkType('disambiguate', 1, page, 'string')
	checkType('disambiguate', 2, disambiguator, 'string', true)
	disambiguator = disambiguator or 'disambiguation'
	return mw.ustring.format('%s (%s)', page, disambiguator)
end

function p.findNamespaceId(link, removeColon)
	-- Finds the namespace id (namespace number) of a link or a pagename. This
	-- function will not work if the link is enclosed in double brackets. Colons
	-- are trimmed from the start of the link by default. To skip colon
	-- trimming, set the removeColon parameter to false.
	checkType('findNamespaceId', 1, link, 'string')
	checkType('findNamespaceId', 2, removeColon, 'boolean', true)
	if removeColon ~= false then
		link = removeInitialColon(link)
	end
	local namespace = link:match('^(.-):')
	if namespace then
		local nsTable = mw.site.namespaces[namespace]
		if nsTable then
			return nsTable.id
		end
	end
	return 0
end

function p.makeWikitextError(msg, helpLink, addTrackingCategory, title)
	-- Formats an error message to be returned to wikitext. If
	-- addTrackingCategory is not false after being returned from
	-- [[Module:Yesno]], and if we are not on a talk page, a tracking category
	-- is added.
	checkType('makeWikitextError', 1, msg, 'string')
	checkType('makeWikitextError', 2, helpLink, 'string', true)
	yesno = require('Module:Yesno')
	title = title or mw.title.getCurrentTitle()
	-- Make the help link text.
	local helpText
	if helpLink then
		helpText = ' ([[' .. helpLink .. '|help]])'
	else
		helpText = ''
	end
	-- Make the category text.
	local category
	if not title.isTalkPage -- Don't categorise talk pages
		and title.namespace ~= 2 -- Don't categorise userspace
		and yesno(addTrackingCategory) ~= false -- Allow opting out
	then
		category = 'Hatnote templates with errors'
		category = mw.ustring.format(
			'[[%s:%s]]',
			mw.site.namespaces[14].name,
			category
		)
	else
		category = ''
	end
	return mw.ustring.format(
		'<strong class="error">Error: %s%s.</strong>%s',
		msg,
		helpText,
		category
	)
end

local curNs = mw.title.getCurrentTitle().namespace
p.missingTargetCat =
	--Default missing target category, exported for use in related modules
	((curNs ==  0) or (curNs == 14)) and
	'Articles with hatnote templates targeting a nonexistent page' or nil

function p.quote(title)
	--Wraps titles in quotation marks. If the title starts/ends with a quotation
	--mark, kerns that side as with {{-'}}
	local quotationMarks = {
		["'"]=true, ['"']=true, [' ']=true, [" "]=true, [' ']=true, [" "]=true
	}
	local quoteLeft, quoteRight = -- Test if start/end are quotation marks
		quotationMarks[string.sub(title,  1,  1)],
		quotationMarks[string.sub(title, -1, -1)]
	if quoteLeft or quoteRight then
		title = mw.html.create("span"):wikitext(title)
	end
	if quoteLeft  then title:css("padding-left",  "0.15em") end
	if quoteRight then title:css("padding-right", "0.15em") end
	return '"' .. tostring(title) .. '"'
end

--------------------------------------------------------------------------------
-- Hatnote
--
-- Produces standard hatnote text. Implements the {{hatnote}} template.
--------------------------------------------------------------------------------

function p.hatnote(frame)
	local args = getArgs(frame)
	local s = args[1]
	if not s then
		return p.makeWikitextError(
			'no text specified',
			'Template:Hatnote#Errors',
			args.category
		)
	end
	return p._hatnote(s, {
		extraclasses = args.extraclasses,
		selfref = args.selfref
	})
end

function p._hatnote(s, options)
	checkType('_hatnote', 1, s, 'string')
	checkType('_hatnote', 2, options, 'table', true)
	options = options or {}
	local inline = options.inline
	local hatnote = mw.html.create(inline == 1 and 'span' or 'div')
	local extraclasses
	if type(options.extraclasses) == 'string' then
		extraclasses = options.extraclasses
	end

	hatnote
		:attr('role', 'note')
		:addClass(p.defaultClasses(inline))
		:addClass(extraclasses)
		:addClass(options.selfref and 'selfref' or nil)
		:wikitext(s)

	return mw.getCurrentFrame():extensionTag{
		name = 'templatestyles', args = { src = 'Module:Hatnote/styles.css' }
	} .. tostring(hatnote)
end

return p