Module:Hatnote list: Difference between revisions

From Zoophilia Wiki
Jump to navigationJump to search
meta>Nihiltres
Removed behaviour of terminating list at empty page parameter; inconsistent with behaviour elsewhere
m 1 revision imported
 
(21 intermediate revisions by 6 users not shown)
Line 1: Line 1:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- For see
--                           Module:Hatnote list                              --
--
--                                                                            --
-- Makes a "For X, see [[Y]]." list from raw parameters. Intended for the
-- This module produces and formats lists for use in hatnotes. In particular, --
-- {{about}} and {{redirect}} templates and their variants. Also incidentally
-- it implements the for-see list, i.e. lists of "For X, see Y" statements,  --
-- introduces andList & orList helpers, useful for other hatnote lists.
-- as used in {{about}}, {{redirect}}, and their variants. Also introduced    --
-- are andList & orList helpers for formatting lists with those conjunctions. --
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


local mArguments --initialize lazily
local mArguments --initialize lazily
local mFormatLink = require('Module:Format link')
local mHatnote = require('Module:Hatnote')
local mHatnote = require('Module:Hatnote')
local libraryUtil = require('libraryUtil')
local libraryUtil = require('libraryUtil')
Line 13: Line 15:
local p = {}
local p = {}


function p.andList (andTable)
--------------------------------------------------------------------------------
-- Stringifies a list with "and"
-- List stringification helper functions
local andString = (#andTable > 2 and mw.text.listToText(andTable, nil, ', and ')) or
--
mw.text.listToText(andTable)
-- These functions are used for stringifying lists, usually page lists inside
return andString
-- the "Y" portion of "For X, see Y" for-see items.
--------------------------------------------------------------------------------
 
--default options table used across the list stringification functions
local stringifyListDefaultOptions = {
conjunction = "and",
separator = ",",
altSeparator = ";",
space = " ",
formatted = false
}
 
--Searches display text only
local function searchDisp(haystack, needle)
return string.find(
string.sub(haystack, (string.find(haystack, '|') or 0) + 1), needle
)
end
 
-- Stringifies a list generically; probably shouldn't be used directly
local function stringifyList(list, options)
-- Type-checks, defaults, and a shortcut
checkType("stringifyList", 1, list, "table")
if #list == 0 then return nil end
checkType("stringifyList", 2, options, "table", true)
options = options or {}
for k, v in pairs(stringifyListDefaultOptions) do
if options[k] == nil then options[k] = v end
end
local s = options.space
-- Format the list if requested
if options.formatted then
list = mFormatLink.formatPages(
{categorizeMissing = mHatnote.missingTargetCat}, list
)
end
-- Set the separator; if any item contains it, use the alternate separator
local separator = options.separator
for k, v in pairs(list) do
if searchDisp(v, separator) then
separator = options.altSeparator
break
end
end
-- Set the conjunction, apply Oxford comma, and force a comma if #1 has "§"
local conjunction = s .. options.conjunction .. s
if #list == 2 and searchDisp(list[1], "§") or #list > 2 then
conjunction = separator .. conjunction
end
-- Return the formatted string
return mw.text.listToText(list, separator .. s, conjunction)
end
end


function p.orList (orTable)
--DRY function
-- Stringifies a list with "or"
function p.conjList (conj, list, fmt)
local orString = (#andTable > 2 and mw.text.listToText(andTable, nil, ', or ')) or
return stringifyList(list, {conjunction = conj, formatted = fmt})
mw.text.listToText(andTable, nil, ' or ')
return orString
end
end


function p.forSee (frame, from, options)
-- Stringifies lists with "and" or "or"
-- Calls _forSee but pulls from the frame.
function p.andList (...) return p.conjList("and", ...) end
mArguments = require('Module:Arguments')
function p.orList (...) return p.conjList("or", ...) end
getArgs = mArguments.getArgs
 
--------------------------------------------------------------------------------
-- For see
--
-- 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
local forSeeDefaultOptions = {
andKeyword = 'and',
title = mw.title.getCurrentTitle().text,
otherText = 'other uses',
forSeeForm = 'For %s, see %s.',
}


local args = getArgs(frame)
--Collapses duplicate punctuation at end of string, ignoring italics and links
return p._forSee(args, from, options)
local function punctuationCollapse (text)
return text:match("[.?!]('?)%1(%]?)%2%.$") and text:sub(1, -2) or text
end
end


function p._forSee (args, from, options)
-- Structures arguments into a table for stringification, & options
-- Produces a "For X, see [[Y]]" string from arguments. Expects index gaps
function p.forSeeArgsToTable (args, from, options)
-- but not blank or whitespace values; those should be filtered. Ignores
-- arguments less than "from", and named arguments.
 
-- Type-checks and defaults
-- Type-checks and defaults
checkType("_forSee", 1, args, 'table')
checkType("forSeeArgsToTable", 1, args, 'table')
checkType("_forSee", 2, from, 'number', true)
checkType("forSeeArgsToTable", 2, from, 'number', true)
from = from or 1
from = from or 1
checkType("_forSee", 3, options, 'table', true)
checkType("forSeeArgsToTable", 3, options, 'table', true)
options = options or {}
options = options or {}
local defaultOptions = {
for k, v in pairs(forSeeDefaultOptions) do
title = mw.title.getCurrentTitle().text,
disambiguator = ' (disambiguation)',
otherText = 'other uses'
}
for k, v in pairs(defaultOptions) do
if options[k] == nil then options[k] = v end
if options[k] == nil then options[k] = v end
end
end
-- maxArg's gotten manually because getArgs() and table.maxn aren't friends
-- maxArg's gotten manually because getArgs() and table.maxn aren't friends
local maxArg = 0
local maxArg = 0
Line 61: Line 117:
if type(k) == 'number' and k > maxArg then maxArg = k end
if type(k) == 'number' and k > maxArg then maxArg = k end
end
end
 
-- Structure the data out from the parameter list:
-- Structure the data out from the parameter list
-- * forTable is the wrapper table, with forRow rows
-- forTable is the wrapper table, with forRow rows
-- * Rows are tables of a "use" string & a "pages" table of pagename strings
-- Each row's a table with one "use" string and one "see" table
-- * Blanks are left empty for defaulting elsewhere, but can terminate list
local forTable = {}
local forTable = {}
local i = from
local i = from
local terminated = false
local terminated = false
-- Repeat to generate and append each row
-- 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
repeat
-- New empty row
-- New empty row
local forRow = {}
local forRow = {}
-- If there's a blank use, assume the list's ended, use the default,
-- On blank use, assume list's ended & break at end of this loop
-- and break at the end of this loop-through.
forRow.use = args[i]
forRow.use = args[i] or options.otherText
if not args[i] then terminated = true end
if not args[i] then terminated = true end
-- New empty list of pages
-- New empty list of pages
forRow.see = {}
forRow.pages = {}
-- If there's not at least one page listed, assume the list's ended, use
-- Insert first pages item if present
-- the default, and break at end of this loop-through.
table.insert(forRow.pages, args[i + 1])
table.insert(forRow.see, args[i + 1] or (options.title .. options.disambiguator))
-- If the param after next is "and", do inner loop to collect params
-- If the option after next is "and", do an inner loop where we collect
-- until the "and"'s stop. Blanks are ignored: "1|and||and|3" → {1, 3}
-- items following "and"'s until the "and"'s stop. If there's a blank
while args[i + 2] == options.andKeyword do
-- where we'd expect an item, ignore it: "1|and||and|3" → {1, 3}
if args[i + 3] then
while args[i + 2] == 'and' do
table.insert(forRow.pages, args[i + 3])
if args[i + 3] then  
table.insert(forRow.see, args[i + 3])
end
end
-- Increment to the next "and"
-- Increment to next "and"
i = i + 2
i = i + 2
end
end
-- Increment to the next use
-- Increment to next use
i = i + 2
i = i + 2
-- Add the row to the table
-- Append the row
table.insert(forTable, forRow)
table.insert(forTable, forRow)
until terminated or i > maxArg
until terminated or i > maxArg


-- Stringify the table, which is easy because it's structured now
return forTable
end
 
-- Stringifies a table as formatted by forSeeArgsToTable
function p.forSeeTableToString (forSeeTable, options)
-- Type-checks and defaults
checkType("forSeeTableToString", 1, forSeeTable, "table", true)
checkType("forSeeTableToString", 2, options, "table", true)
options = options or {}
for k, v in pairs(forSeeDefaultOptions) do
if options[k] == nil then options[k] = v end
end
-- Stringify each for-see item into a list
local strList = {}
local strList = {}
for k, v in pairs(forTable) do
if forSeeTable then
local useStr = v.use
for k, v in pairs(forSeeTable) do
local seeStr = p.andList(mHatnote.formatPages(unpack(v.see)))
local useStr = v.use or options.otherText
table.insert(strList, string.format('For %s, see %s.', useStr, seeStr))
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
return mw.text.listToText(strList, ' ', ' ')
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
-- but not blank/whitespace values. Ignores named args and args < "from".
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.forSee (frame, from, options)
mArguments = require('Module:Arguments')
return p._forSee(mArguments.getArgs(frame), from, options)
end
end


return p
return p

Latest revision as of 20:03, 28 November 2023

Usage from wikitext

This module is not designed be used directly from wikitext even though forSee does take an initial frame argument and could potentially be used from wikitext, e.g.:

  • {{hatnote|PREFIX {{#invoke:Hatnote list|forSee|{{tl|For}}|Module:For|{{tl|About}}|Module:About}} POSTFIX}}
    PREFIX For {{For}}, see Module:For. For {{About}}, see Module:About. POSTFIX

Usage from Lua

To call the module, use <syntaxhighlight lang="lua">local mHatList = require('Module:Hatnote list')</syntaxhighlight> or similar, then access its methods through the mHatList variable (or whatever was used).

andList

andList takes a list in table form, and returns a string with the list separated with "and" and commas as appropriate.

orList

orList takes a list in table form, and returns a string with the list separated with "or" and commas as appropriate.

forSee

_forSee takes three arguments: a table of trimmed arguments with blanks removed, a "from" number with the index to start at, and an options table, and returns a string with a number of "For X, see [[Y]]" sentences. The links are formatted using the methods from Module:Hatnote.

As a convenience, the forSee method (without the leading underscore) takes the same arguments except with a frame instead of an args table, using getArgs() from Module:Arguments to preprocess the arguments.


--------------------------------------------------------------------------------
--                           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 introduced    --
-- 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 checkType = libraryUtil.checkType
local p = {}

--------------------------------------------------------------------------------
-- List stringification 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 stringifyListDefaultOptions = {
	conjunction = "and",
	separator = ",",
	altSeparator = ";",
	space = " ",
	formatted = false
}

--Searches display text only
local function searchDisp(haystack, needle)
	return string.find(
		string.sub(haystack, (string.find(haystack, '|') or 0) + 1), needle
	)
end

-- Stringifies a list generically; probably shouldn't be used directly
local function stringifyList(list, options)
	-- Type-checks, defaults, and a shortcut
	checkType("stringifyList", 1, list, "table")
	if #list == 0 then return nil end
	checkType("stringifyList", 2, options, "table", true)
	options = options or {}
	for k, v in pairs(stringifyListDefaultOptions) do
		if options[k] == nil then options[k] = v end
	end
	local s = options.space
	-- Format the list if requested
	if options.formatted then
		list = mFormatLink.formatPages(
			{categorizeMissing = mHatnote.missingTargetCat}, list
		)
	end
	-- Set the separator; if any item contains it, use the alternate separator
	local separator = options.separator
	for k, v in pairs(list) do
		if searchDisp(v, separator) then
			separator = options.altSeparator
			break
		end
	end
	-- Set the conjunction, apply Oxford comma, and force a comma if #1 has "§"
	local conjunction = s .. options.conjunction .. s
	if #list == 2 and searchDisp(list[1], "§") or #list > 2 then
		conjunction = separator .. conjunction
	end
	-- Return the formatted string
	return mw.text.listToText(list, separator .. s, conjunction)
end

--DRY function
function p.conjList (conj, list, fmt)
	return stringifyList(list, {conjunction = conj, formatted = fmt})
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
--
-- 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
local forSeeDefaultOptions = {
	andKeyword = 'and',
	title = mw.title.getCurrentTitle().text,
	otherText = 'other uses',
	forSeeForm = 'For %s, see %s.',
}

--Collapses duplicate punctuation at end of string, ignoring italics and links
local function punctuationCollapse (text)
 	return text:match("[.?!]('?)%1(%]?)%2%.$") and text:sub(1, -2) or 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
	-- 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
end

-- Stringifies a table as formatted by forSeeArgsToTable
function p.forSeeTableToString (forSeeTable, options)
	-- Type-checks and defaults
	checkType("forSeeTableToString", 1, forSeeTable, "table", true)
	checkType("forSeeTableToString", 2, options, "table", true)
	options = options or {}
	for k, v in pairs(forSeeDefaultOptions) do
		if options[k] == nil then options[k] = v end
	end
	-- Stringify each for-see item into a list
	local strList = {}
	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
	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
-- but not blank/whitespace values. Ignores named args and args < "from".
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.forSee (frame, from, options)
	mArguments = require('Module:Arguments')
	return p._forSee(mArguments.getArgs(frame), from, options)
end

return p