Module:Names

From The Languages of David J. Peterson
Revision as of 03:53, 19 July 2022 by Juelos (talk | contribs)
Jump to navigation Jump to search

This module implements the templates {{given name}}, {{surname}} and {{name translit}}.


local m_languages = require("Module:languages")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_table = require("Module:table")

local export = {}

local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rsubn = mw.ustring.gsub

-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
	local retval = rsubn(term, foo, bar)
	return retval
end

local function join_terms(terms, include_langname, do_language_link)
	local links = {}
	local langnametext
	for _, term in ipairs(terms) do
		if include_langname and not langnametext then
			langnametext = term.lang:getCanonicalName() .. " "
		end
		term.lang = m_languages.getNonEtymological(term.lang)
		if do_language_link and term.lang:getCode() == "en" then
			link = m_links.language_link(term, nil, true)
		else
			link = m_links.full_link(term, nil, true)
		end
		table.insert(links, link)
	end
	return (langnametext or "") .. m_table.serialCommaJoin(links, {conj = "or"})
end

local function join_names(lang, terms, alts, trs, ts, scs)
	local term_objs = {}
	local i = 1
	local do_language_link = false
	if not lang then
		lang = m_languages.getByCode("en")
		do_language_link = true
	end
	while terms[i] do
		table.insert(term_objs, {
			lang = lang, term = terms[i], alt = alts[i], tr = trs[i], gloss = ts[i],
			sc = scs[i] and require("Module:scripts").getByCode(scs[i], "error if invalid"),
		})
		i = i + 1
	end
	return join_terms(term_objs, nil, do_language_link), #term_objs
end

local function get_eqtext(args)
	local eqsegs = {}
	local i = 1
	local lastlang = nil
	local last_eqseg = {}
	while args.eq[i] do
		local eqlang, eqterm = rmatch(args.eq[i], "^(.-):(.*)$")
		if not eqlang then
			eqlang = "en"
			eqterm = args.eq[i]
		end
		if lastlang and lastlang ~= eqlang then
			if #last_eqseg > 0 then
				table.insert(eqsegs, last_eqseg)
			end
			last_eqseg = {}
		end
		lastlang = eqlang
		table.insert(last_eqseg, {
			lang = m_languages.getByCode(eqlang, "eq" .. (i == 1 and "" or i), "allow etym lang"),
			term = eqterm,
			alt = args.eqalt[i],
			tr = args.eqtr[i],
			gloss = args.eqt[i],
			sc = args.eqsc[i] and require("Module:scripts").getByCode(args.eqsc[i], "error if invalid"),
		})
		i = i + 1
	end
	if #last_eqseg > 0 then
		table.insert(eqsegs, last_eqseg)
	end
	local eqtextsegs = {}
	for _, eqseg in ipairs(eqsegs) do
		table.insert(eqtextsegs, join_terms(eqseg, "include langname"))
	end
	return m_table.serialCommaJoin(eqtextsegs)
end

local function get_fromtext(args)
	local fromsegs = {}
	local i = 1
	local last_fromseg = nil
	while args.from[i] do
		local from = args.from[i]
		local prefix, suffix
		if from == "surnames" then
			prefix = "transferred from the "
			suffix = "surname"
		elseif from == "place names" then
			prefix = "transferred from the "
			suffix = "place name"
		elseif from == "coinages" then
			prefix = "originating as "
			suffix = "a coinage"
		else
			prefix = "from "
			local fromlang, fromterm = rmatch(from, "^(.-):(.*)$")
			if fromlang then
				fromlang = m_languages.getByCode(fromlang, "from" .. (i == 1 and "" or i), "allow etym lang")
				suffix = fromlang:getCanonicalName() .. " " .. m_links.full_link({
					lang = m_languages.getNonEtymological(fromlang),
					term = fromterm,
					alt = args.fromalt[i],
					tr = args.fromtr[i],
					gloss = args.fromt[i],
					pos = args.frompos[i],
					lit = args.fromlit[i],
					id = args.fromid[i],
					sc = args.fromsc[i] and require("Module:scripts").getByCode(args.fromsc[i], "error if invalid"),
				}, nil, true)
			elseif rfind(from, " languages$") then
				suffix = "the " .. from
			else
				suffix = from
			end
		end
		if last_fromseg and last_fromseg.prefix ~= prefix then
			table.insert(fromsegs, last_fromseg)
			last_fromseg = nil
		end
		if not last_fromseg then
			last_fromseg = {prefix = prefix, suffixes = {}}
		end
		table.insert(last_fromseg.suffixes, suffix)
		i = i + 1
	end
	table.insert(fromsegs, last_fromseg)
	local fromtextsegs = {}
	for _, fromseg in ipairs(fromsegs) do
		table.insert(fromtextsegs, fromseg.prefix ..
			m_table.serialCommaJoin(fromseg.suffixes, {conj = "or"}))
	end
	return m_table.serialCommaJoin(fromtextsegs, {conj = "or"})
end

-- The main entry point.
function export.given_name(frame)
	local parent_args = frame:getParent().args
	local compat = parent_args.lang
	local offset = compat and 0 or 1

	local function track(page)
		require("Module:debug").track("given name/" .. page)
	end

	local params = {
		[compat and "lang" or 1] = { required = true },
		["gender"] = { default = "unknown-gender" },
		[1 + offset] = { alias_of = "gender", default = "unknown-gender" },
		-- second gender
		["or"] = {},
		["from"] = { list = true },
		[2 + offset] = { alias_of = from, list = true },
		["fromt"] = { list = "from=t", allow_holes = true },
		["fromalt"] = { list = "from=alt", allow_holes = true },
		["fromtr"] = { list = "from=tr", allow_holes = true },
		["frompos"] = { list = "from=pos", allow_holes = true },
		["fromlit"] = { list = "from=lit", allow_holes = true },
		["fromid"] = { list = "from=id", allow_holes = true },
		["fromsc"] = { list = "from=sc", allow_holes = true },
		["usage"] = {},
		["meaning"] = { list = true },
		["dim"] = { list = true },
		["dimalt"] = { list = "dim=alt", allow_holes = true },
		["dimtr"] = { list = "dim=tr", allow_holes = true },
		["dimt"] = { list = "dim=t", allow_holes = true },
		["dimsc"] = { list = "dim=sc", allow_holes = true },
		["diminutive"] = { list = true, alias_of = "dim" },
		["diminutivealt"] = { list = "diminutive=alt", alias_of = "dimalt", allow_holes = true },
		["diminutivetr"] = { list = "diminutive=tr", alias_of = "dimtr", allow_holes = true },
		["diminutivet"] = { list = "diminutive=t", alias_of = "dimt", allow_holes = true },
		["diminutivesc"] = { list = "diminutive=sc", alias_of = "dimsc", allow_holes = true },
		["eq"] = { list = true },
		["eqalt"] = { list = "eq=alt", allow_holes = true },
		["eqtr"] = { list = "eq=tr", allow_holes = true },
		["eqt"] = { list = "eq=t", allow_holes = true },
		["eqsc"] = { list = "eq=sc", allow_holes = true },
		["xlit"] = { list = true },
		["xlitalt"] = { list = "xlit=alt", allow_holes = true },
		["var"] = { list = true },
		["varalt"] = { list = "var=alt", allow_holes = true },
		["vartr"] = { list = "var=tr", allow_holes = true },
		["vart"] = { list = "var=t", allow_holes = true },
		["varsc"] = { list = "var=sc", allow_holes = true },
		["m"] = { list = true },
		["malt"] = { list = "m=alt", allow_holes = true },
		["mtr"] = { list = "m=tr", allow_holes = true },
		["mt"] = { list = "m=t", allow_holes = true },
		["msc"] = { list = "m=sc", allow_holes = true },
		["f"] = { list = true },
		["falt"] = { list = "f=alt", allow_holes = true },
		["ftr"] = { list = "f=tr", allow_holes = true },
		["ft"] = { list = "f=t", allow_holes = true },
		["fsc"] = { list = "f=sc", allow_holes = true },
		-- initial article: A or An
		["A"] = {},
		["sort"] = {},
		["dimtype"] = {},
	}
	
	local args = require("Module:parameters").process(parent_args, params)
	
	local textsegs = {}
	local lang = args[compat and "lang" or 1]
	if not lang and mw.title.getCurrentTitle().nsText == "Template" then
		lang = "und"
	end
	lang = lang and m_languages.getByCode(lang, compat and "lang" or 1)

	local dimtext, numdims =
		join_names(lang, args.dim, args.dimalt, args.dimtr, args.dimt, args.dimsc)
	local xlittext = join_names(nil, args.xlit, args.xlitalt, {}, {}, {})
	local vartext = join_names(lang, args.var, args.varalt, args.vartr, args.vart, args.varsc)
	local mtext = join_names(lang, args.m, args.malt, args.mtr, args.mt, args.msc)
	local ftext = join_names(lang, args.f, args.falt, args.ftr, args.ft, args.fsc)
	local meaningsegs = {}
	for _, meaning in ipairs(args.meaning) do
		table.insert(meaningsegs, '"' .. meaning .. '"')
	end
	local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "or"})

	local eqtext = get_eqtext(args)

	table.insert(textsegs, "<span class='use-with-mention'>")
	local dimtype = args.dimtype
	local article = args.A or
		dimtype and rfind(dimtype, "^[aeiouAEIOU]") and "An" or
		args.gender == "unknown-gender" and "An" or
		"A"

	table.insert(textsegs, article .. " ")
	if numdims > 0 then
		table.insert(textsegs,
			(dimtype and dimtype .. " " or "") ..
			"[[diminutive]]" ..
			(xlittext ~= "" and ", " .. xlittext .. "," or "") ..
			" of the ")
	end
	local genders = {}
	table.insert(genders, args.gender)
	table.insert(genders, args["or"])
	table.insert(textsegs, table.concat(genders, " or ") .. " ")
	table.insert(textsegs, numdims > 1 and "[[given name|given names]]" or
		"[[given name]]")
	local need_comma = false
	if numdims > 0 then
		table.insert(textsegs, " " .. dimtext)
		need_comma = true
	elseif xlittext ~= "" then
		table.insert(textsegs, ", " .. xlittext)
		need_comma = true
	end
	if #args.from > 0 then
		if need_comma then
			table.insert(textsegs, ",")
		end
		need_comma = true
		table.insert(textsegs, " ")
		table.insert(textsegs, get_fromtext(args))
	end
	
	if meaningtext ~= "" then
		if need_comma then
			table.insert(textsegs, ",")
		end
		need_comma = true
		table.insert(textsegs, " meaning " .. meaningtext)
	end
	if args.usage then
		if need_comma then
			table.insert(textsegs, ",")
		end
		need_comma = true
		table.insert(textsegs, " of " .. args.usage .. " usage")
	end
	if vartext ~= "" then
		table.insert(textsegs, ", variant of " .. vartext)
	end
	if mtext ~= "" then
		table.insert(textsegs, ", masculine equivalent " .. mtext)
	end
	if ftext ~= "" then
		table.insert(textsegs, ", feminine equivalent " .. ftext)
	end
	if eqtext ~= "" then
		table.insert(textsegs, ", equivalent to " .. eqtext)
	end
	table.insert(textsegs, "</span>")

	local categories = {}
	local langname = lang:getCanonicalName() .. " "
	local function insert_cats(isdim)
		if isdim == "" then
			-- No category such as "English diminutives of given names"
			table.insert(categories, langname .. isdim .. "given names")
		end
		local function insert_cats_gender(g)
			if g == "unknown-gender" then
				track("unknown gender")
				return
			end
			if g ~= "male" and g ~= "female" and g ~= "dragon" and g ~= "unisex" then
				error("Unrecognized gender: " .. g)
			end
			if g == "unisex" then
				insert_cats_gender("male")
				insert_cats_gender("female")
			end
			table.insert(categories, langname .. isdim .. g .. " given names")
			for i, from in ipairs(args.from) do
				local fromcatform
				local fromlang, fromterm = rmatch(from, "^(.-):(.*)$")
				if fromlang then
					fromcatform = m_languages.getByCode(fromlang, "from" .. (i == 1 and "" or i), "allow etym lang"):getCanonicalName()
				elseif from == "surnames" or from == "place names" or from == "coinages" then
					fromcatform = from
				else
					local family = rmatch(from, "^(.*) languages$")
					if family then
						if require("Module:families").getByCanonicalName(family) then
							fromcatform = from
						end
					elseif m_languages.getByCanonicalName(from, nil, "allow etym") then
						fromcatform = from
					end
				end
				if fromcatform then
					table.insert(categories, langname .. isdim .. g .. " given names from " .. fromcatform)
				else
					track("unrecognized from")
					track("unrecognized from/" .. from)
				end
			end
		end
		insert_cats_gender(args.gender)
		if args["or"] then
			insert_cats_gender(args["or"])
		end
	end
	insert_cats("")
	if numdims > 0 then
		insert_cats("diminutives of ")
	end

	return table.concat(textsegs, "") ..
		m_utilities.format_categories(categories, lang, args.sort)
end

return export

-- For Vim, so we get 4-space tabs
-- vim: set ts=4 sw=4 noet: