Module:Hatnote list
--------------------------------------------------------------------------------
 --                           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 &quot;For X, see Y&quot; statements,   --
 -- as used in {{about}}, {{redirect}}, and their variants. Also introduced    --
 -- are andList &amp; 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 &quot;Y&quot; portion of &quot;For X, see Y&quot; for-see items.
 --------------------------------------------------------------------------------
 
 --default options table used across the list stringification functions
 local stringifyListDefaultOptions = {
 	conjunction = &quot;and&quot;,
 	separator = &quot;,&quot;,
 	altSeparator = &quot;;&quot;,
 	space = &quot; &quot;,
 	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(&quot;stringifyList&quot;, 1, list, &quot;table&quot;)
 	if #list == 0 then return nil end
 	checkType(&quot;stringifyList&quot;, 2, options, &quot;table&quot;, 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 &quot;§&quot;
 	local conjunction = s .. options.conjunction .. s
 	if #list == 2 and searchDisp(list[1], &quot;§&quot;) or #list &gt; 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 &quot;and&quot; or &quot;or&quot;
 function p.andList (...) return p.conjList(&quot;மற்றும்&quot;, ...) end
 function p.orList (...) return p.conjList(&quot;அல்லது&quot;, ...) end
 
 --------------------------------------------------------------------------------
 -- For see
 --
 -- Makes a &quot;For X, see [[Y]].&quot; 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 = 'மற்றும்',
 	title = mw.title.getCurrentTitle().text,
 	otherText = 'பிற பயன்பாட்டுக்கு',
 	forSeeForm = '%s பார்க்க %s.',
 }
 
 --Collapses duplicate punctuation
 local function punctuationCollapse (text)
 	local replacements = {
 		[&quot;%.%.$&quot;] = &quot;.&quot;,
 		[&quot;%?%.$&quot;] = &quot;?&quot;,
 		[&quot;%!%.$&quot;] = &quot;!&quot;,
 		[&quot;%.%]%]%.$&quot;] = &quot;.]]&quot;,
 		[&quot;%?%]%]%.$&quot;] = &quot;?]]&quot;,
 		[&quot;%!%]%]%.$&quot;] = &quot;!]]&quot;
 	}
 	for k, v in pairs(replacements) do text = string.gsub(text, k, v) end
 	return text
 end
 
 -- Structures arguments into a table for stringification, &amp; options
 function p.forSeeArgsToTable (args, from, options)
 	-- Type-checks and defaults
 	checkType(&quot;forSeeArgsToTable&quot;, 1, args, 'table')
 	checkType(&quot;forSeeArgsToTable&quot;, 2, from, 'number', true)
 	from = from or 1
 	checkType(&quot;forSeeArgsToTable&quot;, 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 &gt; 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 &quot;use&quot; string &amp; a &quot;pages&quot; 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 &quot;For other uses, see foo (disambiguation)&quot;
 	if options.extratext and i &gt; maxArg then return nil end
 	-- Loop to generate rows
 	repeat
 		-- New empty row
 		local forRow = {}
 		-- On blank use, assume list's ended &amp; 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 &quot;and&quot;, do inner loop to collect params
 		-- until the &quot;and&quot;'s stop. Blanks are ignored: &quot;1|and||and|3&quot; → {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 &quot;and&quot;
 			i = i + 2
 		end
 		-- Increment to next use
 		i = i + 2
 		-- Append the row
 		table.insert(forTable, forRow)
 	until terminated or i &gt; maxArg
 	
 	return forTable
 end
 
 -- Stringifies a table as formatted by forSeeArgsToTable
 function p.forSeeTableToString (forSeeTable, options)
 	-- Type-checks and defaults
 	checkType(&quot;forSeeTableToString&quot;, 1, forSeeTable, &quot;table&quot;, true)
 	checkType(&quot;forSeeTableToString&quot;, 2, options, &quot;table&quot;, 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 &quot;For X, see [[Y]]&quot; string from arguments. Expects index gaps
 -- but not blank/whitespace values. Ignores named args and args &lt; &quot;from&quot;.
 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