Capil ka isi

Modul:template cat

Matan Wiktionary

Dokumentasi untuk modul ini dapat dibuat di Modul:template cat/doc

-- Imported from enwiktionary
-- Source: https://en.wiktionary.org/wiki/Module:template cat
-- License: CC BY-SA

-- Author: Benwing

local export = {}

local require_when_needed = require("Module:utilities/require when needed")
local is_callable = require_when_needed("Module:fun", "is_callable")
local format_categories = require_when_needed("Module:utilities", "format_categories")
local parse_interface_module = "Module:parse interface"
local m_string_utilities = require("Module:string utilities")
local und_lang = require("Module:languages").getByCode("und", true)

local ugsub = m_string_utilities.gsub
local ufind = m_string_utilities.find
local split = m_string_utilities.split

local insert = table.insert
local concat = table.concat
local unpack = unpack or table.unpack -- Lua 5.2 compatibility


-- This table detects the category type of the template given its name. When this is invoked, the language code has
-- already been removed from the beginning, and anything starting with a slash has been truncated. The entries are
-- processed in order and are two-element lists of Lua patterns (anchored on both sides; beware of hyphens, which need
-- to be %-escaped) and the category type to use. The category types themselves are mapped to categories in
-- category_type_to_category.
local detect_category_type_list = {
	-- order is important here

	-- nouns, proper nouns, pronouns
	-- (1) unambiguous decl/infl templates
	{"decl%-.*proper.*", "noun inflection-table"},
	{"infl%-.*proper.*", "noun inflection-table"},
	{"decl%-.*pron.*", "pronoun inflection-table"},
	{"infl%-.*pron.*", "pronoun inflection-table"},
	{"decl%-noun.*", "noun inflection-table"},
	{"infl%-noun.*", "noun inflection-table"},
	-- (2) nouns
	{"noun", "headword-line"},
	{"noun[ -]form", "headword-line"},
	{"noun[ -]pl", "headword-line"},
	{"noun[ -]plonly", "headword-line"},
	-- Some languages, e.g. Urdu, have inflection templates called e.g. [[Template:ur-noun-f-ī]]. They should be called
	-- [[Template:ur-decl-noun-f-ī]] but we can autodetect them if we exclude the likely cases that are not declension
	-- templates.
	{"noun%-.*", "noun inflection-table"},
	{"ndecl", "noun inflection-table"},
	{"ndecl%-.*", "noun inflection-table"},
	-- (3) proper nouns
	{"proper[ -]?noun", "headword-line"},
	{"proper[ -]?noun[ -]form", "headword-line"},
	{"proper[ -]?noun[ -]pl", "headword-line"},
	{"proper[ -]?noun[ -]plonly", "headword-line"},
	{"pnoun", "headword-line"},
	{"pnoun[ -]form", "headword-line"},
	{"pnoun[ -]pl", "headword-line"},
	{"pnoun[ -]plonly", "headword-line"},
	{"propn", "headword-line"},
	{"propn[ -]form", "headword-line"},
	{"propn[ -]pl", "headword-line"},
	{"propn[ -]plonly", "headword-line"},
	-- See above for inflection templates without 'decl' or 'infl' in them.
	{"proper[ -]?noun%-.*", "noun inflection-table"},
	{"pnoun%-.*", "noun inflection-table"},
	{"propn%-.*", "noun inflection-table"},
	-- (4) pronouns
	{"pron", "headword-line"},
	{"pronoun", "headword-line"},
	{"pron[ -]form", "headword-line"},
	{"pronoun[ -]form", "headword-line"},
	{"prondecl", "pronoun inflection-table"},
	{"prondecl%-.*", "pronoun inflection-table"},
	-- adjectives
	{"decl%-adj.*", "adjective inflection-table"},
	{"infl%-adj.*", "adjective inflection-table"},
	{"adj", "headword-line"},
	{"adjective", "headword-line"},
	{"adj[ -]form", "headword-line"},
	{"adjective[ -]form", "headword-line"},
	{"adj[ -]comp", "headword-line"},
	{"adjective[ -]comp", "headword-line"},
	{"adj[ -]sup", "headword-line"},
	{"adjective[ -]sup", "headword-line"},
	-- Some languages, e.g. Urdu and Lithuanian, have inflection templates called e.g. [[Template:ur-adj-1]] and
	-- [[Template:lt-adj-is]]. They should be called [[Template:ur-decl-adj-1]] and [[Template:lt-decl-adj-is]] but we
	-- can autodetect them if we exclude the likely cases that are not declension templates.
	{"adj%-.*", "adjective inflection-table"},
	{"adecl", "adjective inflection-table"},
	{"adecl%-.*", "adjective inflection-table"},
	-- verbs; need to avoid including conjunctions
	{"verb", "headword-line"},
	{"conj", "verb inflection-table"},
	{"conj[0-9 -].*", "verb inflection-table"},
	{"conjug.*", "verb inflection-table"},
	{"infl%-verb.*", "verb inflection-table"},

	-- pronunciation
	{".*IPA.*", "pronunciation"},
	{"pronunciation", "pronunciation"},
	{"pr", "pronunciation"},
	{"p", "pronunciation"},

	-- form-of
	{".*form of", "form-of"},

	-- pronominal boxes
	{".*personal pronouns", "personal pronoun"},
	{".*demonstrative.*", "demonstrative"},
	{".*interrogative.*", "interrogative"},
	{".*possessives", "possessive"},
	{".*possessive .*", "possessive"},
	{".*reflexives", "reflexive"},
	{".*reflexive .*", "reflexive"},
	-- these next two should precede 'relative'
	{".*correlatives", "pro-form"},
	{".*correlative .*", "pro-form"},
	{".*relative .*", "relative"},
	{".*articles", "article"},
	{".*prefixes", "affix"},
	{".*suffixes", "affix"},

	-- TOC tables
	{".*TOC", "TOC"},

	-- numbers, numerals
	{".*numbers", "number"},
	{".*numerals", "number"},
	{".*ordinals", "ordinal"},
	{".*cardinals", "cardinal"},
	{".*digits", "digit"},

	-- transliteration
	{".*xlit", "orthographic conversion"},
	{".*translit", "orthographic conversion"},

	-- orthographic and regional variants
	{".*variant", "orthographic variant"},
	{".*regional", "regional variant"},
	
	-- sign production templates
	-- FIXME: We should limit this to sign languages. As it is, we put it near the bottom of the
	-- pattern list so it doesn't accidentally override other patterns for non-sign languages.
	{"prod .*", "sign production"},
}

-- This table indicates how to convert template category types to categories. It consists of a list of pairs, where the
-- first element is the category type and the second element is a key-value table containing the following keys:
-- * `aliases`: Optional list of aliases for the category type, which can be used when explicitly specifying the type,
--   e.g. {{tcat|hw}} instead of {{tcat|headword-line}}.
-- * `cats`: List of categories to add the template to. Each entry either specifies a ''raw'' category (if the category
--   name begins with "Category:") or a ''label'' category (for other strings, where e.g. if the label is
--   "noun inflection-table", the category name is "LANG noun inflection-table templates"). An entry is either a string,
--   directly specifying the category name, or a key-value table with keys `name` (the category name) and `sort` (how to
--   generate the sort base). By default, the sort base for raw categories is a comma-separated list of the language
--   names (not codes) associated with the template, or the full template name if there are no languages, and the sort
--   base for label categories is the template name minus the initial language code. If this isn't correct, the `sort`
--   field specifies how to compute the sort base. It is either a function of two arguments, the template name and
--   language object, which should return the sort base; or a table of specs telling how to compute the sort base. In
--   the case of a function, the template name passed in is the full name for raw categories, but the name minus any
--   language code prefix in the case of label categories; and for raw categories, a list of all associated language
--   objects is passed in, or {nil} if none, while for label categories, a single language object is passed in.
--   (Label categories can only exist if there are associated languages.) In the case where `sort` is a table of specs,
--   it is a list where each element is a two-element list of a Lua pattern anchored on both sides and the corresponding
--   pattern replacement string. The specs are processed in order.
local category_type_to_category = {
	-- Inflection-table templates
	{"noun inflection-table", {
		aliases = {"nouninfl", "noundecl", "ndecl"},
		cats = {{name = "noun inflection-table", sort = {
			{"infl%-(.*)", "%1"},
			{"decl%-(.*)", "%1"},
			{"noun%-(.*)", "%1"},
			-- put this twice to catch noun-decl-* and decl-noun-*
			{"infl%-(.*)", "%1"},
			{"decl%-(.*)", "%1"},
			{"noun", "*"},
			{"ndecl%-base%-(.*)", " %1"},
			{"ndecl%-base", " "},
			{"ndecl%-(.*)", "%1"},
			{"ndecl", "*"},
			{"propndecl%-base%-(.*)", " %1"},
			{"propndecl%-base", " "},
			{"propndecl%-(.*)", "%1"},
			{"propndecl", "*"},
			{"proper[ -]?noun%-(.*)", "%1"},
			{"propn%-(.*)", "%1"},
			{"pnoun%-(.*)", "%1"},
		}}},
	}},
	{"pronoun inflection-table", {
		aliases = {"proninfl", "prondecl"},
		cats = {{name = "pronoun inflection-table", sort = {
			{"infl%-(.*)", "%1"},
			{"decl%-(.*)", "%1"},
			{"pronoun%-(.*)", "%1"},
			{"pron%-(.*)", "%1"},
			-- put this twice to catch pron-decl-* and decl-pron-*
			{"infl%-(.*)", "%1"},
			{"decl%-(.*)", "%1"},
			{"pronoun", "*"},
			{"prondecl%-base%-(.*)", " %1"},
			{"prondecl%-base", " "},
			{"prondecl%-(.*)", "%1"},
			{"prondecl", "*"},
		}}},
	}},
	{"adjective inflection-table", {
		aliases = {"adjinfl", "adjdecl", "adecl"},
		cats = {{name = "adjective inflection-table", sort = {
			{"infl%-(.*)", "%1"},
			{"decl%-(.*)", "%1"},
			{"adj%-(.*)", "%1"},
			{"adjective%-(.*)", "%1"},
			-- put this twice to catch adj-decl-* and decl-adj-*
			{"infl%-(.*)", "%1"},
			{"decl%-(.*)", "%1"},
			{"adj", "*"},
			{"adjective", "*"},
			{"adecl%-base%-(.*)", " %1"},
			{"adecl%-base", " "},
			{"adecl%-(.*)", "%1"},
			{"adecl", "*"},
		}}},
	}},
	{"verb inflection-table", {
		aliases = {"verbinfl", "conj"},
		cats = {{name = "verb inflection-table", sort = {
			{"infl%-(.*)", "%1"},
			{"verb%-(.*)", "%1"},
			-- put this twice to catch verb-infl-* and infl-verb-*
			{"infl%-(.*)", "%1"},
			{"conj%-base%-(.*)", " %1"},
			{"conj%-base", " "},
			-- handle conj2, conj1-c, etc.
			{"conj%-?(.*)", "%1"},
			{"conj", "*"},
		}}},
	}},

	-- Headword-line templates
	{"headword-line", {
		aliases = {"hw", "headword"},
		cats = {"headword-line"},
	}},

	-- Definition templates
	{"definition", {
		aliases = {"def", "defn"},
		cats = {"definition"},
	}},
	{"form-of", {
		aliases = {"form of"},
		cats = {"form-of"},
	}},

	-- Etymology and pronunciation templates
	{"etymology", {
		aliases = {"etym"},
		cats = {"etymology"},
	}},
	{"morphology", {
		aliases = {"morph"},
		cats = {"etymology", "Category:Language-specific morphology templates"},
	}},
	{"pronunciation", {
		aliases = {"pron"},
		cats = {"pronunciation"},
	}},
	{"sign production", {
		aliases = {"signprod"},
		cats = {{name = "sign production", sort = {
			{"prod (.*)", "%1"},
		}}},
	}},

	-- Pseudo-namespace templates
	{"reference", {
		aliases = {"ref"},
		cats = {"reference"},
	}},
	{"quotation", {
		aliases = {"quote"},
		cats = {"quotation"},
	}},
	{"usage", {
		cats = {"usage"},
	}},
	{"list", {
		cats = {"list"},
	}},
	{"auto-table", {
		aliases = {"table"},
		cats = {"auto-table"},
	}},

	-- Navigation templates
	
	-- Pro-form box templates
	{"pro-form", {
		aliases = {"pro-forms"},
		cats = {"navigation", "Category:Pro-form box templates"},
	}},
	{"adposition", {
		aliases = {"adpositions", "preposition", "prepositions", "postposition", "postpositions"},
		cats = {"navigation", "Category:Adposition box templates"},
	}},
	{"affix", {
		aliases = {"affixes", "prefix", "prefixes", "suffix", "suffixes"},
		cats = {"navigation", "Category:Pro-form affix box templates"},
	}},
	{"article", {
		aliases = {"articles"},
		cats = {"navigation", "Category:Grammatical article box templates"},
	}},
	{"demonstrative", {
		aliases = {"demonstratives"},
		cats = {"navigation", "Category:Demonstrative box templates"},
	}},
	{"interrogative", {
		aliases = {"interrogatives"},
		cats = {"navigation", "Category:Interrogative box templates"},
	}},
	{"personal pronoun", {
		aliases = {"perspron", "personal pronouns"},
		cats = {"navigation", "Category:Personal pronoun box templates"},
	}},
	{"possessive", {
		aliases = {"possessives"},
		cats = {"navigation", "Category:Possessive pronoun and determiner box templates"},
	}},
	{"reflexive", {
		aliases = {"reflexives"},
		cats = {"navigation", "Category:Reflexive pronoun and determiner box templates"},
	}},
	{"relative", {
		aliases = {"relatives"},
		cats = {"navigation", "Category:Relative pronoun and determiner box templates"},
	}},
	{"navigation", {
		-- miscellaneous navigation box templates like {{eu-aux verbs}}, {{pt-forms of address}}
		aliases = {"nav"},
		cats = {"navigation"},
	}},
	{"TOC", {
		cats = {{name = "navigation", sort = {
			{"categoryTOC", "TOC"},
		}}, "Category:TOC templates"},
	}},
	{"number", {
		aliases = {"numbers"},
		cats = {"navigation", "Category:Language-specific number templates"},
	}},
	{"cardinal", {
		aliases = {"cardinals"},
		cats = {"navigation", "Category:Language-specific number templates"},
	}},
	{"ordinal", {
		aliases = {"ordinals"},
		cats = {"navigation", "Category:Language-specific number templates"},
	}},
	{"digit", {
		aliases = {"digits"},
		cats = {"list", "Category:Language-specific number templates"},
	}},

	-- Entry templates
	{"entry", {
		cats = {"entry"},
	}},

	-- Orthographic conversion templates (e.g. for converting between scripts)
	{"orthographic conversion", {
		aliases = {"transliteration", "translit", "xlit", "orthconv", "scriptconv"},
		cats = {"conversion", "Category:Orthographic conversion templates"},
	}},

	-- Orthographic and regional variant templates (for displaying orthographic, script and/or regional variants)
	{"orthographic variant", {
		aliases = {"orthvar", "scriptvar"},
		-- Currently we categorize orthographic and regional variants the same but we could split them if needed
		cats = {"navigation", "Category:Orthographic and regional variant templates"},
	}},
	{"regional variant", {
		aliases = {"regvar"},
		cats = {"navigation", "Category:Orthographic and regional variant templates"},
	}},

	-- Internal link templates
	{"link", {
		cats = {"link", "Category:Language-specific internal link templates"},
	}},
}

local category_type_to_category_map = {}

for _, category_type_to_category_spec in ipairs(category_type_to_category) do
	local category_type, props = unpack(category_type_to_category_spec)
	category_type_to_category_map[category_type] = props
	if props.aliases then
		for _, alias in ipairs(props.aliases) do
			category_type_to_category_map[alias] = props
		end
	end
end

-- Split an argument on comma, but not comma followed by whitespace; split off sort base after a colon.
local function split_on_comma_and_split_off_sort_base(val)
	local cattypes
	if val:find(",") then
		-- Don't optimize more than this because there can be commas backslashed, inside of links or followed by
		-- whitespace that don't cause splitting.
		cattypes = require(parse_interface_module).split_on_comma(val)
	else
		cattypes = {val}
	end
	for i, cattype_spec in ipairs(cattypes) do
		if cattype_spec:find(":") then
			local cattype, sort_base = cattype_spec:match("^(.-):(.*)$")
			sort_base = sort_base:gsub("_", " ")
			cattypes[i] = {name = cattype, sort_base = sort_base}
		end
	end
	return cattypes
end

local function get_lang_or_script(code)
	return code == "-" and code or
		require("Module:languages").getByCode(code, nil, "allow etym") or
		require("Module:languages").getByCode(code .. "-pro", nil, "allow etym") or
		require("Module:scripts").getByCode(code)
end

local function obj_code(obj)
	if obj == "-" then
		return obj
	end
	return obj:getCode()
end

local function get_prefixed_obj(after_prefix)
	return after_prefix:match("^(%a[%a-]*%a):(.+)$")
end

local function get_suffixed_obj(after_prefix)
	local rest, objcode = after_prefix:match("^(.+)/(%a[%a-]*%a)$")
	return objcode, rest
end

local pseudo_namespace_templates = {
	{"R:", {
		category_type = "reference",
		get_obj_and_rest = get_prefixed_obj,
	}},
	{"RQ:", {
		category_type = "quotation",
		get_obj_and_rest = get_prefixed_obj,
	}},
	{"U:", {
		category_type = "usage",
		get_obj_and_rest = get_prefixed_obj,
	}},
	{"list:", {
		category_type = "list",
		get_obj_and_rest = get_suffixed_obj,
	}},
	{"table:", {
		category_type = "auto-table",
		get_obj_and_rest = get_suffixed_obj,
	}},
}

local function infer_lang_or_script_code_and_category_type(name)
	if name:find(":") then -- only check for pseudo-namespace prefix when a colon is present
		for _, pseudo_namespace_spec in ipairs(pseudo_namespace_templates) do
			local prefix, props = unpack(pseudo_namespace_spec)
			local after_prefix = name:match("^" .. prefix .. "(.+)$")
			if after_prefix then
				local objcode, rest = props.get_obj_and_rest(after_prefix)
				local obj
				if objcode then
					obj = get_lang_or_script(objcode) -- may return nil
					if not obj then
						rest = after_prefix
					end
				else
					rest = after_prefix
				end
				return obj, rest, props.category_type
			end
		end
	end

	local hyphen_parts = split(name, "%-")
	for i = #hyphen_parts - 1, 1, -1 do
		local code = concat(hyphen_parts, "-", 1, i)
		local obj = get_lang_or_script(code)
		if obj then
			local rest = concat(hyphen_parts, "-", i + 1)
			return obj, rest, nil
		end
	end
	return nil, name, nil
end

local function process_sortbase_specs(sortbase, specs)
	for _, spec in ipairs(specs) do
		local from, to = unpack(spec)
		sortbase = ugsub(sortbase, "^" .. from .. "$", to)
	end
	return sortbase
end

local function template_name_minus_langcode_to_category_type(name)
	for _, type_spec in ipairs(detect_category_type_list) do
		local pattern, intended_type = unpack(type_spec)
		if ufind(name, "^" .. pattern .. "$") then
			return intended_type
		end
	end
	return nil
end

local function compute_categories_for_template(full_template_name, template_name_minus_langcode, category_type,
	langs_or_scripts)
	local overriding_sort_base
	if type(category_type) == "table" then
		overriding_sort_base = category_type.sort_base
		category_type = category_type.name
	end
	if not category_type_to_category_map[category_type] then
		error("Unrecognized template category type: " .. category_type)
	end
	local props = category_type_to_category_map[category_type]
	if not props.cats then
		error("Internal error: No categories given for category type: " .. category_type)
	end
	local categories = {}
	for _, catspec in ipairs(props.cats) do
		if type(catspec) == "string" then
			catspec = {name = catspec}
		end
		local rawcat = catspec.name:match("^Category:(.*)")
		if rawcat then
			local sortbase
			-- User-specified sort base does not apply to raw categories, which have a different sort key format
			-- than language-specific categories.
			if not catspec.sort then
				if langs_or_scripts then
					local langnames = {}
					for _, lang_or_sc in ipairs(langs_or_scripts) do
						insert(langnames, lang_or_sc:getCanonicalName()) -- FIXME: or lang:getFullName()?
					end
					sortbase = concat(langnames, ",")
				else
					sortbase = full_template_name
				end
			elseif is_callable(catspec.sort) then
				sortbase = catspec.sort(full_template_name, langs_or_scripts)
			else
				sortbase = process_sortbase_specs(full_template_name, catspec.sort)
			end
			insert(categories, {cat = rawcat, lang = und_lang, sort_base = sortbase})
		elseif langs_or_scripts then
			for _, lang_or_sc in ipairs(langs_or_scripts) do
				local sortbase
				if overriding_sort_base then
					sortbase = overriding_sort_base
				elseif not catspec.sort then
					sortbase = template_name_minus_langcode
				elseif is_callable(catspec.sort) then
					sortbase = catspec.sort(template_name_minus_langcode, lang_or_sc)
				else
					sortbase = process_sortbase_specs(template_name_minus_langcode, catspec.sort)
				end
				if lang_or_sc:hasType("script") then
					insert(categories, {
						cat = ("%s templates"):format(lang_or_sc:getCategoryName()), lang = und_lang, sc = lang_or_sc,
						sort_base = sortbase,
					})
				else
					insert(categories, {
						cat = ("%s %s templates"):format(lang_or_sc:getFullName(), catspec.name), lang = lang_or_sc,
						sort_base = sortbase,
					})
				end
			end
		end
	end
	if not categories[1] then
		error(("No categories generated for template [[Template:%s]] with category type '%s'"):format(
			full_template_name, category_type))
	end

	return categories
end

--[==[
Main entry point.
]==]
function export.categorize(frame)
	local params = {
		[1] = {}, -- comma-separated list of category types; by default, inferred from template name
		lang = {}, -- comma-separated list of languages; by default, inferred from template name
		["pagename"] = {}, -- for testing
		["json"] = {type = "boolean"}, -- for testing
	}

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

	local function insert_cat(cat, sort_key)
		for _, existing_cat in ipairs(category_specs) do
			if existing_cat.cat == cat then
				return
			end
		end
		insert(category_specs, {cat = cat, sort_key = sort_key})
	end

	local pagename = args.pagename
	if not pagename then
		title = mw.title.getCurrentTitle()
		pagename = title.fullText
	end

	if pagename:find("/documentation$") or pagename:find("/documentation/") then
		return ""
	end

	if pagename:find("^Template:User:") then
		insert_cat("User sandbox templates", (pagename:gsub("^Template:User:", "")))
	elseif pagename:find("^User:") then
		insert_cat("User sandbox templates", (pagename:gsub("^User:", "")))
	else
		if not pagename:find("^Template:") then
			error(("This template should only be used in the Template namespace, not on page '%s'"):format(pagename))
		end
		local full_template_name = pagename:gsub("^Template:", "")
		local rootpage = full_template_name:gsub("/.*", "")
		if full_template_name:find("/sandbox") then
			insert_cat("Sandbox templates", full_template_name)
		elseif full_template_name:find("^sandbox/") then
			insert_cat("Sandbox templates", full_template_name:gsub("^sandbox/", ""))
		else
			local template_objs
			if args.lang == "-" then
				template_objs = false
			elseif args.lang then
				template_objs = {}
				for _, code in ipairs(split(args.lang, ",")) do
					-- We need to have an indicator of families because we allow bare family codes to stand for proto-languages.
					if code:find("^fam:") then
						code = code:gsub("^fam:", "")
						local family = require("Module:families").getByCode(code) or
							error(("Unrecognized family code '%s' in [[Module:template cat]]"):format(code))
						local descendants = family:getDescendantCodes()
						for _, desc in ipairs(descendants) do
							local obj = get_lang_or_script(desc)
							if obj then
								-- make sure we skip families without proto-languages
								insert(template_objs, obj)
							end
						end
					else
						local obj = get_lang_or_script(code)
						if not obj then
							error(("Unrecognized language or script code '%s'"):format(code))
						end
						insert(template_objs, obj)
					end
				end
			end

			local cattypes
			if args[1] then
				cattypes = split_on_comma_and_split_off_sort_base(args[1])
			end

			local inferred_obj, inferred_rest, inferred_cattype =
				infer_lang_or_script_code_and_category_type(rootpage)
			if template_objs == nil or not cattypes then
				if template_objs == nil then
					if not inferred_obj then
						if not inferred_cattype then
							error(("Unable to infer language or script from template root page '%s' for template '%s'; specify lang/script and type explicitly"):format(
								rootpage, pagename))
						else
							error(("Unable to infer language or script from template root page '%s' for template '%s', inferred category type '%s'; specify lang/script explicitly"):format(
								rootpage, pagename, inferred_cattype))
						end
					else
						template_objs = {inferred_obj}
					end
				end
				if not cattypes then
					inferred_cattype = inferred_cattype or
						template_name_minus_langcode_to_category_type(inferred_rest)
					if not inferred_cattype then
						error(("Unable to infer template category type from template remainder (after stripping langcode) '%s' for template '%s'; specify type explicitly"):format(
							inferred_rest, pagename))
					end
					cattypes = {inferred_cattype}
				end
			end

			for _, cattype in ipairs(cattypes) do
				local cats = compute_categories_for_template(full_template_name, inferred_rest, cattype, template_objs)
				for _, cat in ipairs(cats) do
					insert(category_specs, cat)
				end
			end
		end
	end

	-- We are returning categories for templates or user-space pages, so we need to force the output.
	local retval = format_categories(category_specs, nil, nil, nil, "force_output")
	if args.json then
		return require("Module:JSON").toJSON {
			category_specs = category_specs,
			retval = mw.text.nowiki(retval),
		}
	else
		return retval
	end
end

--[==[Table used in the documentation to {{tl|template cat}}.]==]
function export.pattern_to_category_type_table()
	local parts = {}
	local function ins(text)
		insert(parts, text)
	end
	ins('{|class="wikitable"')
	ins("! Pattern !! Inferred category type")
	for _, detect_spec in ipairs(detect_category_type_list) do
		local pattern, category_type = unpack(detect_spec)
		ins("|-")
		ins(("| <code>%s</code> || <code>%s</code>"):format(pattern, category_type))
	end
	ins("|}")
	return concat(parts, "\n")
end

--[==[Table used in the documentation to {{tl|template cat}}.]==]
function export.category_type_to_category_table()
	local parts = {}
	local function ins(text)
		insert(parts, text)
	end
	local category_types = {}
	local category_type_to_aliases = {}

	for _, category_type_to_category_spec in ipairs(category_type_to_category) do
		local category_type, props = unpack(category_type_to_category_spec)
		insert(category_types, category_type)
		category_type_to_aliases[category_type] = {}
		if props.aliases then
			for _, alias in ipairs(props.aliases) do
				insert(category_type_to_aliases[category_type], alias)
			end
			table.sort(category_type_to_aliases[category_type])
		end
	end
	table.sort(category_types)

	local function get_category_type_categories(category_type)
		local cats = {}
		for _, catspec in ipairs(category_type_to_category_map[category_type].cats) do
			if type(catspec) == "string" then
				catspec = {name = catspec}
			end
			local cat = catspec.name
			if cat:find("^Category:") then
				insert(cats, ("<code>%s</code>"):format((cat:gsub("^Category:", ""))))
			else
				insert(cats, ("<code><var>LANG</var> %s templates</code>"):format(cat))
			end
		end
		return concat(cats, ", ")
	end

	ins('{|class="wikitable"')
	ins("! Category type !! Canonical category type !! Categories")
	for _, category_type in ipairs(category_types) do
		ins("|-")
		ins(("| <code>'''%s'''</code> || ''(same)'' || <code>%s</code>"):format(
			category_type, get_category_type_categories(category_type)))
		for _, alias in ipairs(category_type_to_aliases[category_type]) do
			ins("|-")
			ins(("| <code>%s</code> || <code>'''%s'''</code> || <code>%s</code>"):format(
				alias, category_type, get_category_type_categories(category_type)))
		end
	end
	ins("|}")
	return concat(parts, "\n")
end

return export