Difference between revisions of "Module:Hatnote list"

From Timelines
Jump to: navigation, search
(Not sure if it's appropriate for Module:Hatnote proper, but it can be its own module and we can just merge it in later, probably)
 
(Updated from sandbox)
 
(13 intermediate revisions by the same user 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. --
 
--------------------------------------------------------------------------------
 
--------------------------------------------------------------------------------
  
Line 13: Line 14:
 
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
 +
}
 +
 
 +
-- Stringifies a list generically; probably shouldn't be used directly
 +
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 = mHatnote.formatPages(unpack(list)) end
 +
-- Set the separator; if any item contains it, use the alternate separator
 +
local separator = options.separator
 +
--searches display text only
 +
function searchDisp(t, f)
 +
return string.find(string.sub(t, (string.find(t, '|') or 0) + 1), f)
 +
end
 +
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 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 conjList("and", ...) end
mArguments = require('Module:Arguments')
+
function p.orList (...) return 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
return p._forSee(args, from, options)
+
function punctuationCollapse (text)
 +
local replacements = {
 +
["%.%.$"] = ".",
 +
["%?%.$"] = "?",
 +
["%!%.$"] = "!",
 +
["%.%]%]%.$"] = ".]]",
 +
["%?%]%]%.$"] = "?]]",
 +
["%!%]%]%.$"] = "!]]"
 +
}
 +
for k, v in pairs(replacements) do text = string.gsub(text, k, v) end
 +
return 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 118:
 
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
+
-- 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 not args[i + 1] then terminated = true end
+
-- until the "and"'s stop. Blanks are ignored: "1|and||and|3" → {1, 3}
-- If the option after next is "and", do an inner loop where we collect
+
while args[i + 2] == options.andKeyword do
-- items following "and"'s until the "and"'s stop. If there's a blank
 
-- where we'd expect an item, ignore it: "1|and||and|3" → {1, 3}
 
while args[i + 2] == 'and' do
 
 
if args[i + 3] then  
 
if args[i + 3] then  
table.insert(forRow.see, args[i + 3])
+
table.insert(forRow.pages, 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
 +
 +
return forTable
 +
end
  
-- Stringify the table, which is easy because it's structured now
+
-- Stringifies a table as formatted by forSeeArgsToTable
 +
function p.forSeeTableToString (forSeeTable, options)
 +
-- Type-checks and defaults
 +
checkType("forSeeTableToString", 1, forSeeTable, "table")
 +
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
+
for k, v in pairs(forSeeTable) do
local useStr = v.use
+
local useStr = v.use or options.otherText
local seeStr = p.andList(mHatnote.formatPages(unpack(v.see)))
+
local pagesStr = p.andList(v.pages, true) or
table.insert(strList, string.format('For %s, see %s.', useStr, seeStr))
+
mHatnote._formatLink(mHatnote.disambiguate(options.title))
 +
local forSeeStr = string.format(options.forSeeForm, useStr, pagesStr)
 +
forSeeStr = punctuationCollapse(forSeeStr)
 +
table.insert(strList, forSeeStr)
 
end
 
end
return mw.text.listToText(strList, ' ', ' ')
+
-- 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 22:18, 24 June 2016

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

--------------------------------------------------------------------------------
--                           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 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
}

-- Stringifies a list generically; probably shouldn't be used directly
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 = mHatnote.formatPages(unpack(list)) end
	-- Set the separator; if any item contains it, use the alternate separator
	local separator = options.separator
	--searches display text only
	function searchDisp(t, f)
		return string.find(string.sub(t, (string.find(t, '|') or 0) + 1), f)
	end
	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 conjList (conj, list, fmt)
	return stringifyList(list, {conjunction = conj, formatted = fmt})
end

-- Stringifies lists with "and" or "or"
function p.andList (...) return conjList("and", ...) end
function p.orList (...) return 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
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
	-- 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
	-- 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")
	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 = {}
	for k, v in pairs(forSeeTable) do
		local useStr = v.use or options.otherText
		local pagesStr = p.andList(v.pages, true) or
			mHatnote._formatLink(mHatnote.disambiguate(options.title))
		local forSeeStr = string.format(options.forSeeForm, useStr, pagesStr)
		forSeeStr = punctuationCollapse(forSeeStr)
		table.insert(strList, forSeeStr)
	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