Jump to content

Module:ParseList

From AiahWiki

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

local getArgs = require('Module:Arguments').getArgs
local p = {}

-- Config options, set in main function
local listDelimiters
local makeElementsLinks
local makeElementsPlural
local makeElementsSingular
local makeElementsLowercase
local useTemplateOnElements
local useTwoArgTemplateOnElements
local finalJoiningWord

local function splitListString(listString)
    local strings = {}
    for str in listString:gmatch("[^" .. listDelimiters .. "]+") do
        table.insert(strings, str:match("^%s*(.-)%s*$"))
    end
    return strings
end

-- This is a TWO-WAY conversion table of singular words and their corresponding
-- plural form, for those that wouldn't be handled correctly by the automatic
-- rules implemented further down below.  Note that we don't need to cover most
-- regular English words, as they're unlikely to ever be used with this module.
-- The kind of stuff you want to add here is gameplay elements like classes,
-- weapons, races, and so on.
local specialPlurals = {
	-- Weapons
	Glaive = "Glaives",
	glaive = "glaives",
	Staff = "Staves",
	staff = "staves",
	-- Races
	Gith = "Gith",
	gith = "gith",
	Githyanki = "Githyanki",
	githyanki = "githyanki",
	Duergar = "Duergar",
	duergar = "duergar",
	Drow = "Drow",
	drow = "drow",
	Dragonborn = "Dragonborn",
	dragonborn = "dragonborn",
	-- Creature types
	Fey = "Fey",
	fey = "fey",
	Undead = "Undead",
	undead = "undead",
}

-- Checks if str is equal to or ends in one of the keys in specialPlurals.
-- Returns the pluralized version if so, otherwise nil.
local function findSpecialPlural(str)
	for singular, plural in pairs(specialPlurals) do
		if str == singular then
			return plural
		end
		local len = #singular
		local suffix = str:sub(-len, -1)
		if (suffix == singular) then
			return str:sub(1, -len - 1) .. plural
		end
	end
end

-- Checks if str is equal to or ends in one of the values in specialPlurals.
-- Returns the singular version if so, otherwise nil.
local function findSpecialSingular(str)
	for singular, plural in pairs(specialPlurals) do
		if str == plural then
			return singular
		end
		local len = #plural
		local suffix = str:sub(-len, -1)
		if suffix == plural then
			return str:sub(1, -len - 1) .. singular
		end
	end
end

-- Checks for a special pluralization first, then implements these rules:
-- ...f -> ...ves
-- ...y -> ...ies
-- ...s -> ...ses
-- ...ch -> ...ches
-- ...sh -> ...shes
-- ... -> ...s
local function makePluralSubString(str)
	local sp = findSpecialPlural(str)
	if sp then
		return sp
	end
	local last1 = str:sub(-1)
	if last1 == "f" then
		return str:sub(1, -2) .. "ves"
	elseif last1 == "y" then
		return str:sub(1, -2) .. "ies"
	elseif last1 == "s" then
		return str .. "es"
	elseif last1 == "x" then
		return str
	end
	local last2 = str:sub(-2)
	if last2 == "ch" or last2 == "sh" then
		return str .. "es"
	end
	return str .. "s"
end

local function makePlural(str)
	local res = {}
	for w in string.gmatch(str, "%S+") do
	       table.insert(res, makePluralSubString(w))
	end
	return table.concat(res, " ")
end

-- Checks for a special singularization first, then implements these rules:
-- ...ves -> ...f
-- ...ies -> ...y
-- ...ses -> ...s
-- ...ches -> ...ch
-- ...shes -> ...sh
-- ...s -> ...
-- ... -> ...
local function makeSingular(str)
	local special = findSpecialSingular(str)
	if special then
		return special
	end
	local last3 = str:sub(-3)
	if last3 == "ves" then
		return str:sub(1, -4) .. "f"
	elseif last3 == "ies" then
		return str:sub(1, -4) .. "y"
	elseif last3 == "ses" then
		return str:sub(1, -3)
	end
	local last4 = str:sub(-4)
	if last4 == "ches" or last4 == "shes" then
		return str:sub(1, -3)
	end
	if str:sub(-1) == "s" then
		return str:sub(1, -2)
	end
	return str
end

-- Applies the various per-element transforms.  Frame is needed for template
-- expansion; it may be nil if template transforms won't be applied.
local function processElement(str, frame)
	local original = str
	if makeElementsPlural then
		str = makePlural(str)
	elseif makeElementsSingular then
		str = makeSingular(str)
	end
	if makeElementsLowercase then
		str = str:lower()
	end
	if makeElementsLinks then
		return "[[" .. original .. "|" .. str .. "]]"
	elseif useTemplateOnElements then
		return frame:expandTemplate{
			title = useTemplateOnElements,
			args = { str }
		}
	elseif useTwoArgTemplateOnElements then
		return frame:expandTemplate{
			title = useTwoArgTemplateOnElements,
			args = { original, str }
		}
	end
	return str
end

-- These functions implement the different output types.  The elements will have
-- already gone through processElement() at this point, so they only need to be
-- glued together to produce the desired type of listing format.
local converters = {
	sentence = function (elements)
	    local count = #elements
	    if count == 0 then
	    	return ""
	    elseif count == 1 then
	    	return elements[1]
	    elseif count == 2 then
			return elements[1] .. " " .. finalJoiningWord .. " " .. elements[2]
	    end
	    local result = ""
		for i, str in ipairs(elements) do
			if i < count then
				result = result .. str .. ", "
			else
				result = result .. finalJoiningWord .. " " .. str
			end
	    end
	    return result
	end,
	htmlList = function (elements)
		local result = ""
		for i, str in ipairs(elements) do
			result = result .. "* " .. str .. "\n"
		end
		return result
	end,
	simpleList = function (elements)
		local result = ""
		local first = true
		for i, str in ipairs(elements) do
			if first then
				result = str
				first = false
			else
				result = result .. ", " .. str
			end
		end
		return result
	end,
	simpleListNoComma = function (elements)
		local result = ""
		local first = true
		for i, str in ipairs(elements) do
			if first then
				result = str
				first = false
			else
				result = result .. "" .. str
			end
		end
		return result
	end,
}

function p.main(frame)
	local args = getArgs(frame, { frameOnly = true })
	return p._main(args, frame)
end

-- Frame is needed for template expansion; may be nil if the useTemplate and
-- useTemplate2 args are nil.
function p._main(args, frame)
	listDelimiters = args['delimiter'] or ","
	makeElementsLinks = args['makeLinks']
	makeElementsPlural = args['makePlural']
	makeElementsSingular = args['makeSingular']
	makeElementsLowercase = args['makeLowercase']
	useTemplateOnElements = args['useTemplate']
	useTwoArgTemplateOnElements = args['useTemplate2']
	finalJoiningWord = args['joinWord'] or "and"
	
	local type = args['type'] or 'sentence'
	local converter = converters[type]
	local elements = {}
	local listString = args[1]
	local strings = splitListString(listString)
	for i, str in ipairs(strings) do
		table.insert(elements, processElement(str, frame))
	end
	return converter(elements)
end

return p