Module:es-headword
Appearance
Documentation for this module may be created at Module:es-headword/doc
local export = {}
local pos_functions = {}
local lang = require("Module:languages").getByCode("es")
local PAGENAME = mw.title.getCurrentTitle().text
local suffix_categories = {
["adjectives"] = true,
["adverbs"] = true,
["nouns"] = true,
["verbs"] = true,
}
local remove_stress = {
["á"] = "a", ["é"] = "e", ["í"] = "i", ["ó"] = "o", ["ú"] = "u"
}
local add_stress = {
["a"] = "á", ["e"] = "é", ["i"] = "í", ["o"] = "ó", ["u"] = "ú"
}
local function track(page)
require("Module:debug").track("es-headword/" .. page)
return true
end
local function glossary_link(entry, text)
text = text or entry
return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local tracking_categories = {}
local poscat = frame.args[1]
or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local params = {
["head"] = {list = true},
["suff"] = {type = "boolean"},
["json"] = {type = "boolean"},
}
local parargs = frame:getParent().args
if poscat == "nouns" and (not parargs[2] or parargs[2] == "") and parargs.pl2 then
track("noun-pl2-without-pl")
end
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = args["head"],
genders = {},
inflections = {},
categories = {}
}
if args["suff"] then
data.pos_category = "suffixes"
if suffix_categories[poscat] then
local singular_poscat = poscat:gsub("s$", "")
table.insert(data.categories, "Spanish " .. singular_poscat .. "-forming suffixes")
else
error("No category exists for suffixes forming " .. poscat .. ".")
end
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data, tracking_categories)
end
if args["json"] then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
.. require("Module:utilities").format_categories(tracking_categories, lang)
end
local function add_ending_to_plurals(plurals, ending)
local retval = {}
for _, pl in ipairs(plurals) do
table.insert(retval, pl .. ending)
end
return retval
end
function export.make_plural_noun(singular, gender)
-- noun + adjective
if singular:find(" ") then
if singular:find(" del? .+$") then
local preceding, prep_phrase = singular:match("^(.+)( del? .+)$")
local preceding_pl = export.make_plural_noun(preceding, gender)
if preceding_pl then
return add_ending_to_plurals(preceding_pl, prep_phrase)
else
return nil
end
elseif singular:find(" al? .+$") then
local preceding, prep_phrase = singular:match("^(.+)( al? .+)$")
local preceding_pl = export.make_plural_noun(preceding, gender)
if preceding_pl then
return add_ending_to_plurals(preceding_pl, prep_phrase)
else
return nil
end
else
local words = mw.text.split(singular, " ")
if #words == 2 then
local noun_p = export.make_plural_noun(words[1], gender)
local adj_p = export.adjective_forms(words[2], gender)
if noun_p then
if gender == "m" and adj_p then
return add_ending_to_plurals(noun_p, " " .. adj_p.mp)
elseif gender == "f" and adj_p then
return add_ending_to_plurals(noun_p, " " .. adj_p.fp)
end
end
end
end
end
-- ends in unstressed vowel or á, é, ó
if mw.ustring.match(singular, "[aeiouáéó]$") then return {singular .. "s"} end
-- ends in í or ú
if mw.ustring.match(singular, "[íú]$") then
return {singular .. "s", singular .. "es"}
end
-- ends in a vowel + z
if mw.ustring.match(singular, "[aeiouáéíóú]z$") then
-- discard all but first return value
local retval = mw.ustring.gsub(singular, "z$", "ces")
return {retval}
end
-- ends in tz
if mw.ustring.match(singular, "tz$") then return {singular} end
local vowels = {}
-- Replace qu before e or i with k so that the u isn't counted as a vowel.
local modified_singular = mw.ustring.gsub(singular, "qu([ie])", "k%1")
for i in mw.ustring.gmatch(modified_singular, "[aeiouáéíóú]") do vowels[#vowels + 1] = i end
-- ends in s or x with more than 1 syllable, last syllable unstressed
if vowels[2] and mw.ustring.match(singular, "[sx]$")
and mw.ustring.match(vowels[#vowels], "[aeiou]") then
return {singular}
end
-- ends in l, r, n, d, z, or j with 3 or more syllables, accented on third to last syllable
if vowels[3] and mw.ustring.match(singular, "[lrndzj]$")
and mw.ustring.match(vowels[#vowels-2], "[áéíóú]") then
return {singular}
end
-- ends in a in a stressed vowel + consonant
if mw.ustring.match(singular, "[áéíóú][^aeiouáéíóú]$") then
-- discard all but first return value
local retval = mw.ustring.gsub(
singular,
"(.)(.)$",
function (vowel, consonant)
return remove_stress[vowel] .. consonant .. "es"
end)
return {retval}
end
-- ends in a vowel + y, l, r, n, d, j, s, x
if mw.ustring.match(singular, "[aeiou][ylrndjsx]$") then
-- two or more vowels: add stress mark to plural
if vowels[2] and mw.ustring.match(singular, "n$") then
local before_stress, after_stress = mw.ustring.match(
modified_singular,
"^(.*)[aeiou]([^aeiou]*[aeiou][nl])$")
local stress = add_stress[vowels[#vowels - 1]]
if before_stress and stress then
-- discard all but first return value
local retval = (before_stress .. stress .. after_stress .. "es"):gsub("k", "qu")
return {retval}
end
end
return {singular .. "es"}
end
-- ends in a vowel + ch
if mw.ustring.match(singular, "[aeiou]ch$") then return {singular .. "es"} end
-- ends in two consonants
if mw.ustring.match(singular, "[^aeiouáéíóú][^aeiouáéíóú]$") then return {singular .. "s"} end
-- ends in a vowel + consonant other than l, r, n, d, z, j, s, or x
if mw.ustring.match(singular, "[aeiou][^aeioulrndzjsx]$") then return {singular .. "s"} end
return nil
end
function export.adjective_forms(singular, gender)
local last_two, last = mw.ustring.match(singular, "(.(.))$")
if mw.ustring.match(singular, "dor$") and gender == "m" then
return {
['ms'] = singular,
['mp'] = singular .. 'es',
['fs'] = singular .. 'a',
['fp'] = singular .. 'as'
}
end
if mw.ustring.match(singular, "dora$") and gender == "f" then
local stem = mw.ustring.sub(singular, 1, #singular-1)
return {
['ms'] = stem,
['mp'] = stem .. 'es',
['fs'] = stem .. 'a',
['fp'] = stem .. 'as'
}
end
if last == "o" or last == "a" and gender == "f" then
local stem = mw.ustring.match(singular, "^(.+)[ao]$")
return {
['ms'] = stem .. "o",
['mp'] = stem .. "os",
['fs'] = stem .. "a",
['fp'] = stem .. "as"
}
end
if last == "e" or mw.ustring.match(singular, "ista$") then
local plural = singular..'s'
return {
['ms'] = singular,
['mp'] = plural,
['fs'] = singular,
['fp'] = plural
}
end
if last == "z" then
local plural = mw.ustring.gsub(singular, "z$", "ces")
return {
['ms'] = singular,
['mp'] = plural,
['fs'] = singular,
['fp'] = plural
}
end
local function make_stem(singular)
return mw.ustring.gsub(
singular,
"^(.+)(.)(.)$",
function (before_stress, stressed_vowel, after_stress)
return before_stress .. (remove_stress[stressed_vowel] or stressed_vowel) .. after_stress
end)
end
if last_two == "ar" or last_two == "ón" or last_two == "ún" or last == "l" then
local plural = make_stem(singular).."es"
return {
['ms'] = singular,
['mp'] = plural,
['fs'] = singular,
['fp'] = plural
}
end
if last_two == "or" then
return {
['ms'] = singular,
['mp'] = singular.."es",
['fs'] = singular,
['fp'] = singular.."es"
}
end
if last_two == "án" or last_two == "és" or last_two == "ín" then
local stem = make_stem(singular)
return {
['ms'] = singular,
['mp'] = stem.."es",
['fs'] = stem.."a",
['fp'] = stem.."as"
}
end
return nil
end
-- Display information for a noun's gender
-- This is separate so that it can also be used for proper nouns
function noun_gender(args, data)
local gender = args[1]
table.insert(data.genders, gender)
if #data.genders == 0 then
table.insert(data.genders, "?")
end
end
pos_functions["proper nouns"] = {
params = {
[1] = {},
},
func = function(args, data)
noun_gender(args, data)
end
}
-- Display additional inflection information for a noun
pos_functions["nouns"] = {
params = {
[1] = {required = true, default = "m"}, --gender
["g2"] = {}, --second gender
["e"] = {type = "boolean"}, --epicene
[2] = {list = "pl"}, --plural override(s)
["f"] = {list = true}, --feminine form(s)
["m"] = {list = true}, --masculine form(s)
["fpl"] = {list = true}, --feminine plural override(s)
["mpl"] = {list = true}, --masculine plural override(s)
["title"] = {},
},
func = function(args, data, tracking_categories)
local langname = lang:getCanonicalName()
local allowed_genders = {
["m"] = true,
["f"] = true,
["m-p"] = true,
["f-p"] = true,
["mf"] = true,
["mf-p"] = true,
["mfbysense"] = true,
["mfbysense-p"] = true,
}
if args["title"] then
track("noun-title")
end
local title = require("Module:links").remove_links(
args["title"] or (#data.heads > 0 and data.heads[1]) or mw.title.getCurrentTitle().text)
if args[1] == "m-f" then
args[1] = "mf"
elseif args[1] == "mfp" or args[1] == "m-f-p" then
args[1] = "mf-p"
end
if not allowed_genders[args[1]] then error("Unrecognized gender: " .. args[1]) end
table.insert(data.genders, args[1])
local function check_missing(form)
if type(form) == "table" then
form = form.term
end
if form and not mw.title.new(form).exists then
table.insert(tracking_categories, "Spanish nouns with red links in their headword lines")
end
end
local function check_all_missing(forms)
for _, form in ipairs(forms) do
check_missing(form)
end
end
if args.g2 then table.insert(data.genders, args.g2) end
if args["e"] then
table.insert(data.categories, langname .. " epicene nouns")
table.insert(data.inflections, {label = glossary_link("epicene")})
end
local plurals = {}
if args[1]:find("%-p$") then
table.insert(data.inflections, {label = glossary_link("plural only")})
if #args[2] > 0 then
error("Can't specify plurals of a plurale tantum noun")
end
else
-- Gather plurals, handling requests for default plurals
for _, pl in ipairs(args[2]) do
if pl == "1" then
track("noun-pl-1")
end
if pl == "1" or pl == "+" then
local default_pls = export.make_plural_noun(title, args[1])
for _, defp in ipairs(default_pls) do
table.insert(plurals, defp)
end
else
table.insert(plurals, pl)
end
end
-- Check for special plural signals
local mode = nil
if plurals[1] == "?" or plurals[1] == "!" or plurals[1] == "-" or plurals[1] == "~" then
mode = plurals[1]
table.remove(plurals, 1) -- Remove the mode parameter
end
if mode == "?" then
-- Plural is unknown
table.insert(data.categories, langname .. " nouns with unknown or uncertain plurals")
elseif mode == "!" then
-- Plural is not attested
table.insert(data.inflections, {label = "plural not attested"})
table.insert(data.categories, langname .. " nouns with unattested plurals")
return
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
table.insert(data.categories, langname .. " uncountable nouns")
-- If plural forms were given explicitly, then show "usually"
if #plurals > 0 then
table.insert(data.inflections, {label = "usually " .. glossary_link("uncountable")})
table.insert(data.categories, langname .. " countable nouns")
else
table.insert(data.inflections, {label = glossary_link("uncountable")})
end
else
-- Countable or mixed countable/uncountable
if #plurals == 0 then
local pls = export.make_plural_noun(title, args[1])
if pls then
for _, pl in ipairs(pls) do
table.insert(plurals, pl)
end
end
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
table.insert(data.inflections, {label = glossary_link("countable") .. " and " .. glossary_link("uncountable")})
table.insert(data.categories, langname .. " uncountable nouns")
table.insert(data.categories, langname .. " countable nouns")
else
-- Countable nouns
table.insert(data.categories, langname .. " countable nouns")
end
end
end
local masculines = {}
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
-- Gather feminines. For each feminine, generate the corresponding plural(s).
for _, f in ipairs(args.f) do
if f == "1" then
track("noun-f-1")
end
if f == "1" or f == "+" then
-- Generate default feminine.
local noun_forms = export.adjective_forms(title, "m")
if not noun_forms then
error("Unable to generate default feminine of '" .. title .. "'")
end
f = noun_forms.fs
end
table.insert(feminines, f)
local fpls = export.make_plural_noun(f, "f")
if fpls then
for _, pl in ipairs(fpls) do
-- Add an accelerator for each feminine plural whose lemma
-- is the feminine singular, so that the accelerated entry
-- that is generated has a definition that looks like
-- # {{plural of|es|FEMININE}}
table.insert(feminine_plurals, {term = pl, accel = {form = "p", lemma = f}})
end
end
end
-- Gather feminines. For each masculine, generate the corresponding plural(s).
for _, m in ipairs(args.m) do
if m == "1" then
track("noun-m-1")
end
if m == "1" or m == "+" then
-- Generate default masculine.
local noun_forms = export.adjective_forms(title, "f")
if not noun_forms then
error("Unable to generate default masculine of '" .. title .. "'")
end
m = noun_forms.ms
end
table.insert(masculines, m)
local mpls = export.make_plural_noun(m, "m")
if mpls then
for _, pl in ipairs(mpls) do
table.insert(masculine_plurals, pl)
end
end
end
if #args.fpl > 0 then
-- Override any existing feminine plurals.
if #args.fpl == #feminines then
-- If same number of overriding feminine plurals as feminines,
-- assume each feminine plural goes with the corresponding feminine
-- and use each corresponding feminine as the lemma in the accelerator.
-- The generated entry will have # {{plural of|es|FEMININE}} as the
-- definition.
feminine_plurals = {}
for i, fpl in ipairs(args.fpl) do
table.insert(feminine_plurals, {term = fpl, accel = {form = "p", lemma = feminines[i]}})
end
else
-- Otherwise, don't add any accelerators.
feminine_plurals = args.fpl
end
end
if #args.mpl > 0 then
-- Override any existing masculine plurals.
masculine_plurals = args.mpl
end
check_all_missing(plurals)
check_all_missing(feminines)
check_all_missing(feminine_plurals)
check_all_missing(masculines)
check_all_missing(masculine_plurals)
local function redundant_plural(pl)
for _, p in ipairs(plurals) do
if p == pl then
return true
end
end
return false
end
for _, mpl in ipairs(masculine_plurals) do
if redundant_plural(mpl) then
track("noun-redundant-mpl")
end
end
for _, fpl in ipairs(feminine_plurals) do
if redundant_plural(fpl) then
track("noun-redundant-fpl")
end
end
if #plurals > 0 then
plurals.label = "plural"
plurals.accel = {form = "p"}
table.insert(data.inflections, plurals)
end
if #feminines > 0 then
feminines.label = "feminine"
feminines.accel = {form = "f"}
table.insert(data.inflections, feminines)
end
if #feminine_plurals > 0 then
feminine_plurals.label = "feminine plural"
table.insert(data.inflections, feminine_plurals)
end
if #masculines > 0 then
masculines.label = "masculine"
table.insert(data.inflections, masculines)
end
if #masculine_plurals > 0 then
masculine_plurals.label = "masculine plural"
masculine_plurals.accel = {form = "p"}
table.insert(data.inflections, masculine_plurals)
end
end
}
return export