Module:User:Stujul/affix/templates

local export = {}

local m_affix = require("Module:affix") local m_languages = require("Module:languages") local pseudo_loan_module = "Module:affix/pseudo-loan" local put -- initialized once, when needed, to require("Module:parse utilities")

local rsplit = mw.text.split

-- Per-param modifiers, which can be specified either as separate parameters (e.g. t2=, pos3=) or as inline modifiers -- , , etc. The key is the name fo the parameter (e.g. "t", "pos") and the value is a table with -- elements as follows: -- * `extra_specs`: An optional table of extra key-value pairs to add to the spec used for parsing the parameter --                 when specified as a separate parameter (e.g. {type = "boolean"} for a Boolean parameter, or --                  {alias_of = "t"} for the "gloss" parameter, which is aliased to "t"), on top of the default, which --                 is {list = true, allow_holes = true, require_index = true}. -- * `convert`: An optional function to convert the raw argument into the form passed to Module:affix. --             This function takes four parameters: (1) `arg` (the raw argument); (2) `inline` (true if we're --              processing an inline modifier, false otherwise); (3) `term_index` (the actual index of the first term); --             (4) `i` (the logical index of the term being processed, starting from 1). -- * `item_dest`: The name of the key used when storing the parameter's value into the processed `parts` list. --               Normally the same as the parameter's name. Different in the case of "t", where we store the gloss in --               "gloss", and "g", where we store the genders in "genders". -- * `param_key`: The name of the key used in the `params` spec passed to Module:parameters. Normally the same as --               the parameter's name. Different in the case of "lit", "sc", etc. where e.g. we distinguish per-term --               parameters "lit1", "lit2", etc. from the overall parameter "lit". local param_mods = { t = { -- We need to store the t1=/t2= param and the  inline modifier into the "gloss" key of the parsed part, -- because that is what Module:affix expects. item_dest = "gloss", },	gloss = { -- The `extra_specs` handles the fact that "gloss" is an alias of "t". extra_specs = {alias_of = "t"}, },	tr = {}, ts = {}, g = { -- We need to store the g1=/g2= param and the  inline modifier into the "genders" key of the parsed part, -- because that is what Module:affix expects. item_dest = "genders", convert = function(arg, inline, term_index, i)			return rsplit(arg, ",") end, },	id = {}, alt = {}, q = {}, qq = {}, lit = { -- lit1=, lit2=, ... are different from lit=; the former describe the literal meaning of an individual argument -- while the latter applies to the expression as a whole and appears after them at the end. To handle this in		-- separate parameters, we need to set the key in the `params` object passed to Module:parameters to -- something else (in this case "partlit") and set `list = "lit"` in the value of the `params` object. This -- causes Module:parameters to fetch parameters named lit1=, lit2= etc. but store them into "partlit", while -- lit= is stored into "lit". param_key = "partlit", extra_specs = {list = "lit"}, },	pos = { -- pos1=, pos2=, ... are different from pos=; the former indicate the part of speech of an individual argument -- and appear in parens after the term (similar to the pos= argument to ), while the latter applies to the -- expression as a whole and controls the names of various categories. We handle the distinction identically to -- lit1= etc. vs. lit=; see above. param_key = "partpos", extra_specs = {list = "pos"}, },	lang = { -- lang1=, lang2=, ... are different from 1=; the former set the language of individual arguments that are -- different from the overall language specified in 1=. Note that the preferred way of specifying a different -- language for a given individual argument is using a language-code prefix, e.g. 'la:minūtia' or -- 'grc:σκατός', instead of using langN=. Since for compatibility purposes we may support lang= as -- a synonym of 1=, we can't store langN= in "lang". Instead we do the same as for lit1= etc. vs. lit= above. -- In addition, we need a conversion function to convert from language codes to language objects, which needs -- to conditionalize the `param` parameter of `getByCode` of Module:languages on whether the param is -- inline. (This only affects the error message.) param_key = "partlang", extra_specs = {list = "lang"}, convert = function(arg, inline, term_index, i)			-- term_index + i - 1 because we want to reference the actual term param name, which offsets from -- `term_index` (the index of the first term); subtract 1 since i is one-based. return m_languages.getByCode(arg, inline and "" .. (term_index + i - 1) .. ":lang" or "lang" .. i, "allow etym") end, },	sc = { -- sc1=, sc2=, ... are different from sc=; the former apply to individual arguments when lang1=, lang2=, ... -- is specified, while the latter applies to all arguments where langN=... isn't specified. We handle the -- distinction identically to lit1= etc. vs. lit=; see above. In addition, we need a conversion function to		-- convert from script codes to script objects, which needs to conditionalize the `param` parameter of -- `getByCode` of Module:scripts on whether the param is inline. (This only affects the error message.) param_key = "partsc", extra_specs = {list = "sc"}, convert = function(arg, inline, term_index, i) -- term_index + i - 1 same as above for "lang". return require("Module:scripts").getByCode(arg, inline and "" .. (term_index + i - 1) .. ":sc" or "sc" .. i)		end, }, }

local function get_valid_prefixes local valid_prefixes = {} for param_mod, _ in pairs(param_mods) do		table.insert(valid_prefixes, param_mod) end table.sort(valid_prefixes) return valid_prefixes end

local function fetch_script(sc, param) return sc and require("Module:scripts").getByCode(sc, param) or nil end

-- Parse raw arguments in `args`. If `extra_params` is specified, it should be a one-argument function that is called -- on the `params` structure before parsing; its purpose is to specify additional allowed parameters or possibly disable -- parameters. If `has_source` is given, there is a source-language parameter following 1= (which becomes the -- "destination" language parameter) and preceding the terms. This is currently used for. The -- source-language parameter is allowed to be an etymology-only language while the language in 1= is currently not so -- allowed (FIXME: should we change this?). Returns five values ARGS, TERM_INDEX, LANG_OBJ, SCRIPT_OBJ, SOURCE_LANG_OBJ -- where ARGS is a table of the parsed arguments; TERM_INDEX is the argument containing all the terms; LANG_OBJ is the -- language object corresponding to the language code specified in 1=; SCRIPT_OBJ is the script object corresponding to -- sc= (if given, otherwise nil); and SOURCE_LANG_OBJ is the language object corresponding to the source-language code -- specified in 2= if `has_source` is specified (otherwise nil). local function parse_args(args, extra_params, has_source, ilangcode) if args.lang then error("The |lang= parameter is not used by this template. Place the language code in parameter 1 instead.") end

local term_index = (ilangcode and 1 or 2) + (has_source and 1 or 0) local params = { [term_index] = {list = true, allow_holes = true},

["lit"] = {}, ["sc"] = {}, ["pos"] = {}, ["sort"] = {}, }

if not ilangcode then params[1] = {required = true, default = "und"} end

local source_index if has_source then source_index = term_index - 1 params[source_index] = {required = true, default = "und"} end

local default_param_spec = {list = true, allow_holes = true, require_index = true} for param_mod, param_mod_spec in pairs(param_mods) do		local param_key = param_mod_spec.param_key or param_mod if not param_mod_spec.extra_specs then params[param_key] = default_param_spec else local param_spec = mw.clone(default_param_spec) for k, v in pairs(param_mod_spec.extra_specs) do				param_spec[k] = v			end params[param_key] = param_spec end end

if extra_params then extra_params(params) end

args = require("Module:parameters").process(args, params) local lang if ilangcode then lang = m_languages.getByCode(ilangcode, true) else lang = m_languages.getByCode(args[1], 1) end local source if has_source then source = m_languages.getByCode(args[source_index], source_index, "allow etym") end return args, term_index, lang, fetch_script(args["sc"], "sc"), source end

-- Return an object containing all the properties of the `i`th term. `template` is the name of the calling template -- (used only in the debug-tracking mechanism). `args` is the arguments as returned by parse_args. `term_index` is -- the argument containing all the terms. This handles all the complexities of fetching all the properties associated -- with a term either out of separate parameters (e.g. pos3=, g2=) or from inline modifiers (e.g. -- 'term'). It also handles parsing off a separate language code attached to the beginning of a term or -- specified using langN= or . local function get_parsed_part(template, args, term_index, i)	local part = {} local term = args[term_index][i]

if not (term or args["alt"][i] or args["tr"][i] or args["ts"][i]) then require("Module:debug/track")(template .. "/no term or alt or tr") return nil end

-- Parse all the term-specific parameters and store in `part`. for param_mod, param_mod_spec in pairs(param_mods) do		local dest = param_mod_spec.item_dest or param_mod local param_key = param_mod_spec.param_key or param_mod local arg = args[param_key][i] if arg then if param_mod_spec.convert then arg = param_mod_spec.convert(arg, false, term_index, i)			end part[dest] = arg end end

-- Remove and remember an initial exclamation point from the term, and parse off an initial language code (e.g.	-- 'la:minūtia' or 'grc:σκατός'). if term then local termlang, actual_term = term:match("^([A-Za-z0-9._-]+):(.*)$") if termlang and termlang ~= "w" then -- special handling for w:... links to Wikipedia -- -1 since i is one-based termlang = m_languages.getByCode(termlang, term_index + i - 1, "allow etym") term = actual_term else termlang = nil end if part.lang and termlang then error(("Both lang%s= and a language in %s= given; specify one or the other"):format(i, i + 1)) end part.lang = part.lang or termlang part.term = term end

-- Check for inline modifier, e.g. מרים. But exclude HTML entry with, , or -- similar in it, caused by wrapping an argument in, or similar. Basically, all tags of -- the sort we parse here should consist of a less-than sign, plus letters, plus a colon, e.g. , so if -- we see a tag on the outer level that isn't in this format, we don't try to parse it. The restriction to the -- outer level is to allow generated HTML inside of e.g. qualifier tags, such as foo. if term and term:find("<") and not term:find("^[^<]*<[a-z]*[^a-z:]") then if not put then put = require("Module:parse utilities") end local run = put.parse_balanced_segment_run(term, "<", ">") local function parse_err(msg) error(msg .. ": " .. (i + 1) .. "=" .. table.concat(run)) end part.term = run[1]

for j = 2, #run - 1, 2 do			if run[j + 1] ~= "" then parse_err("Extraneous text '" .. run[j + 1] .. "' after modifier") end local modtext = run[j]:match("^<(.*)>$") if not modtext then parse_err("Internal error: Modifier '" .. modtext .. "' isn't surrounded by angle brackets") end local prefix, arg = modtext:match("^([a-z]+):(.*)$") if not prefix then parse_err(("Modifier %s lacks a prefix, should begin with one of %s followed by a colon"):format( run[j], table.concat(get_valid_prefixes, ","))) end if not param_mods[prefix] then parse_err(("Unrecognized prefix '%s' in modifier %s, should be one of %s"):format( prefix, run[j], table.concat(get_valid_prefixes, ","))) end local dest = param_mods[prefix].item_dest or prefix if part[dest] then parse_err("Modifier '" .. prefix .. "' occurs twice, second occurrence " .. run[j]) end if param_mods[prefix].convert then arg = param_mods[prefix].convert(arg, true, term_index, i)			end part[dest] = arg end end

return part end

-- Return an array of objects describing all the terms specified by the user. The meat of the work is done by -- get_parsed_part. `template`, `args` and `term_index` are as in get_parsed_part and are required. Optional -- `max_terms_allowed` restricts the number of terms that can be specified, and optional `start_index` specifies the -- first index in the user-specified terms under the `term_index` argument to pull terms out of, defaulting to 1. -- Currently only suffix specifies a value for `start_index`, because the first term is handled differently by -- suffix compared with all the remaining terms. local function get_parsed_parts(template, args, term_index, max_terms_allowed, start_index) local parts = {} start_index = start_index or 1

-- Find the maximum index among any of the list parameters. local maxmaxindex = 0 for k, v in pairs(args) do		if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then if max_terms_allowed and v.maxindex > max_terms_allowed then -- Try to determine the original parameter name associated with v.maxindex. if type(k) == "number" then -- Subtract one because e.g. if terms start at 2, the 4th term is in 5=. arg = k + v.maxindex - 1 else arg = k .. v.maxindex end error(("In %s, at most %s terms can be specified but argument %s specified, corresponding to term #%s")					:format(template, template, max_terms_allowed, arg, v.maxindex)) end maxmaxindex = v.maxindex end end

for index = start_index, maxmaxindex do		local part = get_parsed_part(template, args, term_index, index) parts[index - start_index + 1] = part end

return parts end

function export.affix(frame) local function extra_params(params) params["type"] = {} params["nocap"] = {type = "boolean"} params["notext"] = {type = "boolean"} params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(frame:getParent.args, extra_params)

if args["type"] and not m_affix.compound_types[args["type"]] then error("Unrecognized compound type: '" .. args["type"] .. "'") end

local parts = get_parsed_parts("affix", args, term_index)

-- There must be at least one part to display. If there are gaps, a term -- request will be shown. if not next(parts) and not args["type"] then if mw.title.getCurrentTitle.nsText == "Template" then parts = { {term = "prefix-"}, {term = "base"}, {term = "-suffix"} } else error("You must provide at least one part.") end end

return m_affix.show_affix { lang = lang, sc = sc, parts = parts, pos = args["pos"], sort_key = args["sort"], type = args["type"], nocap = args["nocap"], notext = args["notext"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.compound(frame) local function extra_params(params) params["type"] = {} params["nocap"] = {type = "boolean"} params["notext"] = {type = "boolean"} params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(frame:getParent.args, extra_params)

if args["type"] and not m_affix.compound_types[args["type"]] then error("Unrecognized compound type: '" .. args["type"] .. "'") end

local parts = get_parsed_parts("compound", args, term_index)

-- There must be at least one part to display. If there are gaps, a term -- request will be shown. if not next(parts) and not args["type"] then if mw.title.getCurrentTitle.nsText == "Template" then parts = { {term = "first"}, {term = "second"} } else error("You must provide at least one part of a compound.") end end

return m_affix.show_compound { lang = lang, sc = sc, parts = parts, pos = args["pos"], sort_key = args["sort"], type = args["type"], nocap = args["nocap"], notext = args["notext"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.compound_like(frame) local iparams = { ["lang"] = {}, ["template"] = {}, ["text"] = {}, ["oftext"] = {}, ["cat"] = {}, }

local iargs = require("Module:parameters").process(frame.args, iparams, nil, "affix/templates", "compound_like") local parent_args = frame:getParent.args

local function extra_params(params) params["pos"] = nil params["nocap"] = {type = "boolean"} params["notext"] = {type = "boolean"} params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(parent_args, extra_params, nil, iargs.lang)

local template = iargs["template"] local nocat = args["nocat"] local notext = args["notext"] local text = not notext and iargs["text"] local oftext = not notext and (iargs["oftext"] or text and "of") local cat = not nocat and iargs["cat"]

local parts = get_parsed_parts(template, args, term_index)

if not next(parts) then if mw.title.getCurrentTitle.nsText == "Template" then parts = { {term = "first"}, {term = "second"} } end end

return m_affix.show_compound_like { lang = lang, sc = sc, parts = parts, sort_key = args["sort"], text = text, oftext = oftext, cat = cat, nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.surface_analysis(frame) local function ine(arg) -- Since we're operating before calling Module:parameters, we need to imitate how that module processes arguments, -- including trimming since numbered arguments don't have automatic whitespace trimming. if not arg then return arg end arg = mw.text.trim(arg) if arg == "" then arg = nil end return arg end

local parent_args = frame:getParent.args local etymtext local arg1 = ine(parent_args[1]) if not arg1 then -- Allow omitted first argument to just display "By surface analysis". etymtext = "" elseif arg1:find("^%+") then -- If the first argument (normally a language code) is prefixed with a +, it's a template name. local template_name = arg1:sub(2) local new_args = {} for i, v in pairs(parent_args) do			if type(i) == "number" then if i > 1 then new_args[i - 1] = v				end else new_args[i] = v			end end new_args.nocap = true etymtext = ", " .. frame:expandTemplate { title = template_name, args = new_args } end

if etymtext then return (ine(parent_args.nocap) and "b" or "B") .. "y surface analysis" .. etymtext end

local function extra_params(params) params["type"] = {} params["nocap"] = {type = "boolean"} params["notext"] = {type = "boolean"} params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(parent_args, extra_params)

if args["type"] and not m_affix.compound_types[args["type"]] then error("Unrecognized compound type: '" .. args["type"] .. "'") end

local parts = get_parsed_parts("surface analysis", args, term_index)

-- There must be at least one part to display. If there are gaps, a term -- request will be shown. if not next(parts) then if mw.title.getCurrentTitle.nsText == "Template" then parts = { {term = "first"}, {term = "second"} } else error("You must provide at least one part.") end end

return m_affix.show_surface_analysis { lang = lang, sc = sc, parts = parts, pos = args["pos"], sort_key = args["sort"], type = args["type"], nocap = args["nocap"], notext = args["notext"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.circumfix(frame) local function extra_params(params) params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(frame:getParent.args, extra_params)

local parts = get_parsed_parts("circumfix", args, term_index, 3) local prefix = parts[1] local base = parts[2] local suffix = parts[3]

-- Just to make sure someone didn't use the template in a silly way if not (prefix and base and suffix) then if mw.title.getCurrentTitle.nsText == "Template" then prefix = {term = "circumfix", alt = "prefix"} base = {term = "base"} suffix = {term = "circumfix", alt = "suffix"} else error("You must specify a prefix part, a base term and a suffix part.") end end

return m_affix.show_circumfix { lang = lang, sc = sc, prefix = prefix, base = base, suffix = suffix, pos = args["pos"], sort_key = args["sort"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.confix(frame) local function extra_params(params) params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(frame:getParent.args, extra_params)

local parts = get_parsed_parts("confix", args, term_index, 3) local prefix = parts[1] local base = #parts >= 3 and parts[2] or nil local suffix = #parts >= 3 and parts[3] or parts[2]

-- Just to make sure someone didn't use the template in a silly way if not (prefix and suffix) then if mw.title.getCurrentTitle.nsText == "Template" then prefix = {term = "prefix"} suffix = {term = "suffix"} else error("You must specify a prefix part, an optional base term and a suffix part.") end end

return m_affix.show_confix { lang = lang, sc = sc, prefix = prefix, base = base, suffix = suffix, pos = args["pos"], sort_key = args["sort"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.pseudo_loan(frame) local function extra_params(params) params["pos"] = nil params["nocap"] = {type = "boolean"} params["notext"] = {type = "boolean"} params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc, source = parse_args(frame:getParent.args, extra_params, "has source")

local parts = get_parsed_parts("pseudo-loan", args, term_index)

return require(pseudo_loan_module).show_pseudo_loan { lang = lang, source = source, sc = sc, parts = parts, sort_key = args["sort"], nocap = args["nocap"], notext = args["notext"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.infix(frame) local function extra_params(params) params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(frame:getParent.args, extra_params)

local parts = get_parsed_parts("infix", args, term_index, 2) local base = parts[1] local infix = parts[2]

-- Just to make sure someone didn't use the template in a silly way if not (base and infix) then if mw.title.getCurrentTitle.nsText == "Template" then base = {term = "base"} infix = {term = "infix"} else error("You must provide a base term and an infix.") end end

return m_affix.show_infix { lang = lang, sc = sc, base = base, infix = infix, pos = args["pos"], sort_key = args["sort"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.prefix(frame) local function extra_params(params) params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(frame:getParent.args, extra_params)

local prefixes = get_parsed_parts("prefix", args, term_index) local base = nil

if #prefixes >= 2 then base = prefixes[#prefixes] prefixes[#prefixes] = nil end

-- Just to make sure someone didn't use the template in a silly way if #prefixes == 0 then if mw.title.getCurrentTitle.nsText == "Template" then base = {term = "base"} prefixes = { {term = "prefix"} } else error("You must provide at least one prefix.") end end

return m_affix.show_prefix { lang = lang, sc = sc, prefixes = prefixes, base = base, pos = args["pos"], sort_key = args["sort"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.suffix(frame) local function extra_params(params) params["nocat"] = {type = "boolean"} params["force_cat"] = {type = "boolean"} end

local args, term_index, lang, sc = parse_args(frame:getParent.args, extra_params)

local base = get_parsed_part("suffix", args, term_index, 1) local suffixes = get_parsed_parts("suffix", args, term_index, nil, 2)

-- Just to make sure someone didn't use the template in a silly way if #suffixes == 0 then if mw.title.getCurrentTitle.nsText == "Template" then base = {term = "base"} suffixes = { {term = "suffix"} } else error("You must provide at least one suffix.") end end

return m_affix.show_suffix { lang = lang, sc = sc, base = base, suffixes = suffixes, pos = args["pos"], sort_key = args["sort"], nocat = args["nocat"], lit = args["lit"], force_cat = args["force_cat"] } end

function export.derivsee(frame) local iargs = frame.args local iparams = { ["derivtype"] = {}, ["mode"] = {}, }	local iargs = require("Module:parameters").process(frame.args, iparams)

local params = { ["head"] = {}, ["id"] = {}, ["sc"] = {}, ["pos"] = {}, }	local derivtype = iargs.derivtype if derivtype == "PIE root" then params[1] = {} else params[1] = {required = "true", default = "und"} params[2] = {} end

local args = require("Module:parameters").process(frame:getParent.args, params)

local lang local term

if derivtype == "PIE root" then lang = m_languages.getByCode("ine-pro") term = args[1] or args["head"]

if term then term = "*" .. term .. "-"		end else lang = m_languages.getByCode(args[1], 1) term = args[2] or args["head"] end

local id = args.id	local sc = fetch_script(args.sc, "sc") local pos = require("Module:string utilities").pluralize(args.pos or "term")

if not term then local SUBPAGE = mw.title.getCurrentTitle.subpageText if lang:hasType("reconstructed") or mw.title.getCurrentTitle.nsText == "Reconstruction" then term = "*" .. SUBPAGE elseif lang:hasType("appendix-constructed") then term = SUBPAGE else term = SUBPAGE end end

if derivtype == "PIE root" then return frame:callParserFunction{ name = "#categorytree", args = { "Terms derived from the Proto-Indo-European root " .. term .. (id and " (" .. id .. ")" or ""), depth = 0, class = "\"derivedterms\"", mode = iargs.mode, }			}	end

local category = nil local langname = lang:getCanonicalName if (derivtype == "compound" and pos == nil) then category = langname .. " compounds with " .. term elseif derivtype == "compound" then category = langname .. " compound " .. pos .. " with " .. term elseif derivtype == "separable verb" then category = langname .. " separable verbs with " .. term else category = langname .. " " .. pos .. " " .. derivtype .. "ed with " .. term .. (id and " (" .. id .. ")" or "") end

return frame:callParserFunction{ name = "#categorytree", args = { category, depth = 0, class = "\"derivedterms" .. (sc and " " .. sc:getCode or "") .. "\"", namespaces = "-" .. (mw.title.getCurrentTitle.nsText == "Reconstruction" and " Reconstruction" or ""), }		} end

return export