# 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 "For X, see Y" statements, --  
-- as used in , , 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("மற்றும்", ...) end  
function p.orList (...) return p.conjList("அல்லது", ...) end  

---
-- For see  
---
-- Makes a "For X, see Y." list from raw parameters. Intended for the  
-- and 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 = {  
["%.%.$"] = ".",  
["%?%.$"] = "?",  
["%!%.$"] = "!",  
["%.%]%]%.$"] = ".]]",  
["%?%]%]%.$"] = "?]]",  
["%!%]%]%.$"] = "!]]"  
}  
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 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 .  
function p.forSee (, from, options)  
mArguments = require('Module:Arguments')  
return p._forSee(mArguments.getArgs, from, options)  
end  

return p