Jump to content

Module:Hatnote list: Difference between revisions

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
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--                           Module:Hatnote list                              --
-- Format link
--                                                                            --
--
-- This module produces and formats lists for use in hatnotes. In particular, --
-- Makes a wikilink from the given link and display values. Links are escaped
-- it implements the for-see list, i.e. lists of "For X, see Y" statements,  --
-- with colons if necessary, and links to sections are detected and displayed
-- as used in {{about}}, {{redirect}}, and their variants. Also introduced    --
-- with " § " as a separator rather than the standard MediaWiki "#". Used in
-- are andList & orList helpers for formatting lists with those conjunctions. --
-- the {{format link}} template.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
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 mError -- lazily initialise [[Module:Error]]
local yesno -- lazily initialise [[Module:Yesno]]
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
end


-- Stringifies a list generically; probably shouldn't be used directly
local function maybeItalicize(s, shouldItalicize)
local function stringifyList(list, options)
-- Italicize s if s is a string and the shouldItalicize parameter is true.
-- Type-checks, defaults, and a shortcut
if s and shouldItalicize then
checkType("stringifyList", 1, list, "table")
return '<i>' .. s .. '</i>'
if #list == 0 then return nil end
else
checkType("stringifyList", 2, options, "table", true)
return s
options = options or {}
for k, v in pairs(stringifyListDefaultOptions) do
if options[k] == nil then options[k] = v end
end
end
local s = options.space
end
-- Format the list if requested
 
if options.formatted then
local function parseLink(link)
list = mFormatLink.formatPages(
-- Parse a link and return a table with the link's components.
{categorizeMissing = mHatnote.missingTargetCat}, list
-- These components are:
)
-- - link: the link, stripped of any initial colon (always present)
-- - page: the page name (always present)
-- - section: the page name (may be nil)
-- - display: the display text, if manually entered after a pipe (may be nil)
link = removeInitialColon(link)
 
-- Find whether a faux display value has been added with the {{!}} magic
-- word.
local prePipe, display = link:match('^(.-)|(.*)$')
link = prePipe or link
 
-- Find the page, if it exists.
-- For links like [[#Bar]], the page will be nil.
local preHash, postHash = link:match('^(.-)#(.*)$')
local page
if not preHash then
-- We have a link like [[Foo]].
page = link
elseif preHash ~= '' then
-- We have a link like [[Foo#Bar]].
page = preHash
end
end
-- Set the separator; if any item contains it, use the alternate separator
 
local separator = options.separator
-- Find the section, if it exists.
for k, v in pairs(list) do
local section
if searchDisp(v, separator) then
if postHash and postHash ~= '' then
separator = options.altSeparator
section = postHash
break
end
end
end
-- Set the conjunction, apply Oxford comma, and force a comma if #1 has "§"
local conjunction = s .. options.conjunction .. s
return {
if #list == 2 and searchDisp(list[1], "§") or #list > 2 then
link = link,
conjunction = separator .. conjunction
page = page,
section = section,
display = display,
}
end
 
local function formatDisplay(parsed, options)
-- Formats a display string based on a parsed link table (matching the
-- output of parseLink) and an options table (matching the input options for
-- _formatLink).
local page = maybeItalicize(parsed.page, options.italicizePage)
local section = maybeItalicize(parsed.section, options.italicizeSection)
if (not section) then
return page
elseif (not page) then
return mw.ustring.format('§&nbsp;%s', section)
else
return mw.ustring.format('%s §&nbsp;%s', page, section)
end
end
-- Return the formatted string
return mw.text.listToText(list, separator .. s, conjunction)
end
end


--DRY function
local function missingArgError(target)
function p.conjList (conj, list, fmt)
mError = require('Module:Error')
return stringifyList(list, {conjunction = conj, formatted = fmt})
return mError.error{message =
'Error: no link or target specified! ([[' .. target .. '#Errors|help]])'
}
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
-- Main functions
--
-- Makes a "For X, see [[Y]]." list from raw parameters. Intended for the
-- {{about}} and {{redirect}} templates and their variants.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


--default options table used across the forSee family of functions
function p.formatLink(frame)
local forSeeDefaultOptions = {
-- The formatLink export function, for use in templates.
andKeyword = 'and',
yesno = require('Module:Yesno')
title = mw.title.getCurrentTitle().text,
local args = getArgs(frame)
otherText = 'other uses',
local link = args[1] or args.link
forSeeForm = 'For %s, see %s.',
local target = args[3] or args.target
}
if not (link or target) then
return missingArgError('Template:Format link')
end


--Collapses duplicate punctuation at end of string, ignoring italics and links
return p._formatLink{
local function punctuationCollapse (text)
link = link,
return text:match("[.?!]('?)%1(%]?)%2%.$") and text:sub(1, -2) or text
display = args[2] or args.display,
target = target,
italicizePage = yesno(args.italicizepage),
italicizeSection = yesno(args.italicizesection),
categorizeMissing = args.categorizemissing
}
end
end


-- Structures arguments into a table for stringification, & options
function p._formatLink(options)
function p.forSeeArgsToTable (args, from, options)
-- The formatLink export function, for use in modules.
-- Type-checks and defaults
checkType('_formatLink', 1, options, 'table')
checkType("forSeeArgsToTable", 1, args, 'table')
local function check(key, expectedType) --for brevity
checkType("forSeeArgsToTable", 2, from, 'number', true)
checkTypeForNamedArg(
from = from or 1
'_formatLink', key, options[key], expectedType or 'string', true
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
end
-- maxArg's gotten manually because getArgs() and table.maxn aren't friends
check('link')
local maxArg = 0
check('display')
for k, v in pairs(args) do
check('target')
if type(k) == 'number' and k > maxArg then maxArg = k end
check('italicizePage', 'boolean')
check('italicizeSection', 'boolean')
check('categorizeMissing')
 
-- Normalize link and target and check that at least one is present
if options.link == '' then options.link = nil end
if options.target == '' then options.target = nil end
if not (options.link or options.target) then
return missingArgError('Module:Format link')
end
end
-- Structure the data out from the parameter list:
-- * forTable is the wrapper table, with forRow rows
-- * Rows are tables of a "use" string & a "pages" table of pagename strings
-- * 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
local parsed = parseLink(options.link)
end
local display = options.display or parsed.display
local catMissing = options.categorizeMissing
local category = ''
 
-- Find the display text
if not display then display = formatDisplay(parsed, options) end


-- Stringifies a table as formatted by forSeeArgsToTable
-- Handle the target option if present
function p.forSeeTableToString (forSeeTable, options)
if options.target then
-- Type-checks and defaults
local parsedTarget = parseLink(options.target)
checkType("forSeeTableToString", 1, forSeeTable, "table", true)
parsed.link = parsedTarget.link
checkType("forSeeTableToString", 2, options, "table", true)
parsed.page = parsedTarget.page
options = options or {}
for k, v in pairs(forSeeDefaultOptions) do
if options[k] == nil then options[k] = v end
end
end
-- Stringify each for-see item into a list
 
local strList = {}
-- Test if page exists if a diagnostic category is specified
if forSeeTable then
if catMissing and (mw.ustring.len(catMissing) > 0) then
for k, v in pairs(forSeeTable) do
local title = nil
local useStr = v.use or options.otherText
if parsed.page then title = mw.title.new(parsed.page) end
local pagesStr =
if title and (not title.isExternal) then
p.andList(v.pages, true) or
local success, exists = pcall(function() return title.exists end)
mFormatLink._formatLink{
if success and not exists then
categorizeMissing = mHatnote.missingTargetCat,
category = mw.ustring.format('[[Category:%s]]', catMissing)
link = mHatnote.disambiguate(options.title)
end
}
local forSeeStr = string.format(options.forSeeForm, useStr, pagesStr)
forSeeStr = punctuationCollapse(forSeeStr)
table.insert(strList, forSeeStr)
end
end
end
end
if options.extratext then table.insert(strList, punctuationCollapse(options.extratext..'.')) end
-- Return the concatenated list
-- Format the result as a link
return table.concat(strList, ' ')
if parsed.link == display then
return mw.ustring.format('[[:%s]]%s', parsed.link, category)
else
return mw.ustring.format('[[:%s|%s]]%s', parsed.link, display, category)
end
end
end


-- Produces a "For X, see [[Y]]" string from arguments. Expects index gaps
--------------------------------------------------------------------------------
-- but not blank/whitespace values. Ignores named args and args < "from".
-- Derived convenience functions
function p._forSee (args, from, options)
--------------------------------------------------------------------------------
local forSeeTable = p.forSeeArgsToTable(args, from, options)
return p.forSeeTableToString(forSeeTable, options)
end


-- As _forSee, but uses the frame.
function p.formatPages(options, pages)
function p.forSee (frame, from, options)
-- Formats an array of pages using formatLink and the given options table,
mArguments = require('Module:Arguments')
-- and returns it as an array. Nil values are not allowed.
return p._forSee(mArguments.getArgs(frame), from, options)
local ret = {}
for i, page in ipairs(pages) do
ret[i] = p._formatLink{
link = page,
categorizeMissing = options.categorizeMissing,
italicizePage = options.italicizePage,
italicizeSection = options.italicizeSection
}
end
return ret
end
end


return p
return p

Latest revision as of 11:15, 13 March 2026

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

Script error: Lua error: Internal error: The interpreter exited with status 127.

--------------------------------------------------------------------------------
-- Format link
--
-- Makes a wikilink from the given link and display values. Links are escaped
-- with colons if necessary, and links to sections are detected and displayed
-- with " § " as a separator rather than the standard MediaWiki "#". Used in
-- the {{format link}} template.
--------------------------------------------------------------------------------
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg
local mArguments -- lazily initialise [[Module:Arguments]]
local mError -- lazily initialise [[Module:Error]]
local yesno -- lazily initialise [[Module:Yesno]]

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

local function maybeItalicize(s, shouldItalicize)
	-- Italicize s if s is a string and the shouldItalicize parameter is true.
	if s and shouldItalicize then
		return '<i>' .. s .. '</i>'
	else
		return s
	end
end

local function parseLink(link)
	-- Parse a link and return a table with the link's components.
	-- These components are:
	-- - link: the link, stripped of any initial colon (always present)
	-- - page: the page name (always present)
	-- - section: the page name (may be nil)
	-- - display: the display text, if manually entered after a pipe (may be nil)
	link = removeInitialColon(link)

	-- Find whether a faux display value has been added with the {{!}} magic
	-- word.
	local prePipe, display = link:match('^(.-)|(.*)$')
	link = prePipe or link

	-- Find the page, if it exists.
	-- For links like [[#Bar]], the page will be nil.
	local preHash, postHash = link:match('^(.-)#(.*)$')
	local page
	if not preHash then
		-- We have a link like [[Foo]].
		page = link
	elseif preHash ~= '' then
		-- We have a link like [[Foo#Bar]].
		page = preHash
	end

	-- Find the section, if it exists.
	local section
	if postHash and postHash ~= '' then
		section = postHash
	end
	
	return {
		link = link,
		page = page,
		section = section,
		display = display,
	}
end

local function formatDisplay(parsed, options)
	-- Formats a display string based on a parsed link table (matching the
	-- output of parseLink) and an options table (matching the input options for
	-- _formatLink).
	local page = maybeItalicize(parsed.page, options.italicizePage)
	local section = maybeItalicize(parsed.section, options.italicizeSection)
	if (not section) then
		return page
	elseif (not page) then
		return mw.ustring.format('§&nbsp;%s', section)
	else
		return mw.ustring.format('%s §&nbsp;%s', page, section)
	end
end

local function missingArgError(target)
	mError = require('Module:Error')
	return mError.error{message =
		'Error: no link or target specified! ([[' .. target .. '#Errors|help]])'
	}
end

--------------------------------------------------------------------------------
-- Main functions
--------------------------------------------------------------------------------

function p.formatLink(frame)
	-- The formatLink export function, for use in templates.
	yesno = require('Module:Yesno')
	local args = getArgs(frame)
	local link = args[1] or args.link
	local target = args[3] or args.target
	if not (link or target) then
		return missingArgError('Template:Format link')
	end

	return p._formatLink{
		link = link,
		display = args[2] or args.display,
		target = target,
		italicizePage = yesno(args.italicizepage),
		italicizeSection = yesno(args.italicizesection),
		categorizeMissing = args.categorizemissing
	}
end

function p._formatLink(options)
	-- The formatLink export function, for use in modules.
	checkType('_formatLink', 1, options, 'table')
	local function check(key, expectedType) --for brevity
		checkTypeForNamedArg(
			'_formatLink', key, options[key], expectedType or 'string', true
		)
	end
	check('link')
	check('display')
	check('target')
	check('italicizePage', 'boolean')
	check('italicizeSection', 'boolean')
	check('categorizeMissing')

	-- Normalize link and target and check that at least one is present
	if options.link == '' then options.link = nil end
	if options.target == '' then options.target = nil end
	if not (options.link or options.target) then
		return missingArgError('Module:Format link')
	end

	local parsed = parseLink(options.link)
	local display = options.display or parsed.display
	local catMissing = options.categorizeMissing
	local category = ''

	-- Find the display text
	if not display then display = formatDisplay(parsed, options) end

	-- Handle the target option if present
	if options.target then
		local parsedTarget = parseLink(options.target)
		parsed.link = parsedTarget.link
		parsed.page = parsedTarget.page
	end

	-- Test if page exists if a diagnostic category is specified
	if catMissing and (mw.ustring.len(catMissing) > 0) then
		local title = nil
		if parsed.page then title = mw.title.new(parsed.page) end
		if title and (not title.isExternal) then
			local success, exists = pcall(function() return title.exists end)
			if success and not exists then
				category = mw.ustring.format('[[Category:%s]]', catMissing)
			end
		end
	end
	
	-- Format the result as a link
	if parsed.link == display then
		return mw.ustring.format('[[:%s]]%s', parsed.link, category)
	else
		return mw.ustring.format('[[:%s|%s]]%s', parsed.link, display, category)
	end
end

--------------------------------------------------------------------------------
-- Derived convenience functions
--------------------------------------------------------------------------------

function p.formatPages(options, pages)
	-- Formats an array of pages using formatLink and the given options table,
	-- and returns it as an array. Nil values are not allowed.
	local ret = {}
	for i, page in ipairs(pages) do
		ret[i] = p._formatLink{
			link = page,
			categorizeMissing = options.categorizeMissing,
			italicizePage = options.italicizePage,
			italicizeSection = options.italicizeSection
		}
	end
	return ret
end

return p
Original content on DGR Wiki is © DGR Entertainment & Multimedia Ltd.