Module:User:Benwing2/category tree/topic cat

local export = {}

local label_data = require("Module:User:Benwing2/category tree/topic cat/data") local topic_cat_utilities_module = "Module:User:Benwing2/category tree/topic cat/utilities" local labels_ancillary_module = "Module:User:Benwing2/labels/ancillary"

local rsplit = mw.text.split local rgsplit = mw.text.gsplit

-- Category object

local Category = {} Category.__index = Category

function Category.new_main(frame) local self = setmetatable({}, Category)

local params = { [1] = {},		[2] = {required = true}, ["sc"] = {}, }	args = require("Module:parameters").process(frame:getParent.args, params, nil, "category tree/topic cat", "new_main") self._info = {code = args[1], label = args[2]} self:initCommon if not self._data then return nil end return self end

function Category.new(info) for key, val in pairs(info) do		if not (key == "code" or key == "label") then error("The parameter “" .. key .. "” was not recognized.") end end local self = setmetatable({}, Category) self._info = info if not self._info.label then error("No label was specified.") end self:initCommon if not self._data then error("The label “" .. self._info.label .. "” does not exist.") end return self end

export.new = Category.new export.new_main = Category.new_main

function Category:initCommon if self._info.code then self._lang = require("Module:languages").getByCode(self._info.code, true) end -- Convert label to lowercase if possible local lowercase_label = mw.getContentLanguage:lcfirst(self._info.label) -- Check if the label exists local labels = label_data["LABELS"]

if labels[lowercase_label] then self._info.label = lowercase_label end self._data = labels[self._info.label] -- Go through handlers if not self._data then for _, handler in ipairs(label_data["HANDLERS"]) do			self._data = handler.handler(self._info.label) if self._data then self._data.module = handler.module break end end end end

function Category:getInfo return self._info end

function Category:format_displaytitle(include_lang_prefix) local displaytitle = self._data.displaytitle if not displaytitle then return nil end if type(displaytitle) == "string" then if include_lang_prefix and self._lang then displaytitle = ("%s:%s"):format(self._lang:getCode, displaytitle) end else displaytitle = displaytitle(self._info.label, lang, include_lang_prefix) end

return displaytitle end

function Category:getBreadcrumbName local ret

if self._lang then ret = self._data.breadcrumb or self:format_displaytitle(false) else ret = self._data.umbrella and self._data.umbrella.breadcrumb or			self._data.breadcrumb or self:format_displaytitle(false) end if not ret then ret = self._info.label end

if type(ret) == "string" or type(ret) == "number" then ret = {name = ret} end

local name = self:substitute_template_specs(ret.name) local nocap = ret.nocap

return name, nocap end

function Category:getDataModule return self._data.module end

function Category:canBeEmpty if self._lang then return false else return true end end

function Category:isHidden return false end

function Category:getCategoryName if self._lang then return self._lang:getCode .. ":" .. mw.getContentLanguage:ucfirst(self._info.label) else return mw.getContentLanguage:ucfirst(self._info.label) end end

function Category:replace_special_descriptions(desc) if not desc then return desc end

local type_to_text = { topic = "related to", set = "for various", name = "for names of", type = "for types of", }	local special_description_formats = { ["default with the"] = "related to the", }

local function format_partial_desc(desc) local desc_parts = {} local types = self._data.type or "topic" for typ in rgsplit(types, "%s*,%s*") do			if not type_to_text[typ] then error(("Invalid type '%s', should be one or more of 'topic', 'set', 'name' or 'type', comma-separated")					:format(types)) end table.insert(desc_parts, type_to_text[typ] .. " " .. desc) return require("Module:table").serialCommaJoin(desc_parts) end end

local function convert_to_full_desc(partial_desc) return " terms " .. partial_desc .. "."	end

if desc:find("^=") then desc = desc:gsub("^=", "") return convert_to_full_desc(format_partial_desc(desc)) end

local stripped_desc = desc local no_singularize, wikify while true do		local new_stripped_desc = stripped_desc:match("^(.+) no singularize$") if new_stripped_desc then stripped_desc = new_stripped_desc no_singularize = true else new_stripped_desc = stripped_desc:match("^(.+) wikify$") if new_stripped_desc then stripped_desc = new_stripped_desc wikify = true else break end end end if stripped_desc == "default" or special_description_formats[stripped_desc] then local label_sub = "label" if wikify then label_sub = label_sub .. "_wiki" end if no_singularize then label_sub = label_sub .. "_no_sing" end label_sub = (""):format(label_sub)

local partial_desc if special_description_formats[stripped_desc] then partial_desc = special_description_formats[stripped_desc] .. " " .. label_sub else partial_desc = format_partial_desc(label_sub) end

return convert_to_full_desc(partial_desc) end

return desc end

function Category:substitute_template_specs(desc) if not desc then return desc end if type(desc) == "number" then desc = tostring(desc) end -- FIXME, when does this occur? It doesn't occur in the corresponding place in Module:category tree/poscatboiler. if type(desc) ~= "string" then return desc end desc = desc:gsub("", mw.title.getCurrentTitle.text)

if desc:find("") then local eninfo = mw.clone(self._info) eninfo.code = "en" local en = Category.new(eninfo) desc = desc:gsub("", "This category contains no dictionary entries, only other categories. The subcategories are of two sorts:\n\n" ..			"* Subcategories named like \"aa:" .. mw.getContentLanguage:ucfirst(self._info.label) .. 			"\" (with a prefixed language code) are categories of terms in specific languages. " ..			"You may be interested especially in Category:" .. en:getCategoryName .. ", for English terms.\n" ..			"* Subcategories of this one named without the prefixed language code are further categories just like this one, but devoted to finer topics."		) end if self._lang then desc = desc:gsub("", self._lang:getCanonicalName) desc = desc:gsub("", self._lang:getCode) desc = desc:gsub("", self._lang:getCategoryName) desc = desc:gsub("", self._lang:makeCategoryLink) end

local function handle_label(label_sub, no_singularize, wikify) local function gsub_desc(to) return (desc:gsub(label_sub, require("Module:pattern utilities").replacement_escape(to))) end

return gsub_desc(require(topic_cat_utilities_module).link_label(self._info.label, no_singularize, wikify)) end

for _, spec in ipairs { {""},		{"", "no singularize"}, {"", false, "wikify"}, {"", "no singularize", "wikify"}, } do		local label_sub, no_singularize, wikify = unpack(spec) if desc:find(label_sub) then desc = handle_label(label_sub, no_singularize, wikify) end end

if desc:find("{") then desc = mw.getCurrentFrame:preprocess(desc) end return desc end

function Category:substitute_template_specs_in_args(args) if not args then return args end local pinfo = {} for k, v in pairs(args) do		k = self:substitute_template_specs(k) v = self:substitute_template_specs(v) pinfo[k] = v	end return pinfo end

function Category:getTopright local def_topright_parts = {} local function process_box(val, pattern) if not val then return end local defval = mw.getContentLanguage:ucfirst(self._info.label) if type(val) ~= "table" then val = {val} end for _, v in ipairs(val) do			if v == true then table.insert(def_topright_parts, pattern:format(defval)) else table.insert(def_topright_parts, pattern:format(v)) end end end

process_box(self._data.wp, "") process_box(self._data.wpcat, "") process_box(self._data.commonscat, "")

local def_topright if #def_topright_parts > 0 then def_topright = table.concat(def_topright_parts, "\n") end

if self._lang then return self:substitute_template_specs(self._data.topright or def_topright) else return self._data.umbrella and self:substitute_template_specs(self._data.umbrella.topright) or			self:substitute_template_specs(def_topright) end end

local function remove_lang_params(desc) desc = desc:gsub("^ ", "") desc = desc:gsub(":", "") desc = desc:gsub("^ ", "") desc = desc:gsub("^ ", "") return desc end

function Category:getDescription(isChild) -- Allows different text in the list of a category's children local isChild = isChild == "child"

local function display_title local displaytitle = self:format_displaytitle("include lang prefix") if displaytitle then displaytitle = self:substitute_template_specs(displaytitle) mw.getCurrentFrame:callParserFunction("DISPLAYTITLE", "Category:" .. displaytitle) end end

if not isChild and self._data.displaytitle then display_title end

local function get_labels_categorizing local topic_producing_labels = require(labels_ancillary_module).find_labels_for_topic(self._info.label, self._lang) local function make_code(txt) return (" "):format(txt) end local formatted_labels = {} for label, aliases in pairs(topic_producing_labels) do 			if #aliases == 0 then table.insert(formatted_labels, make_code(label)) elseif #aliases == 1 then table.insert(formatted_labels, ("%s (alias %s)"):format(make_code(label), make_code(aliases[1]))) else table.sort(aliases) for i, alias in ipairs(aliases) do 					aliases[i] = make_code(alias) end table.insert(formatted_labels, ("%s (aliases %s)"):format(make_code(label), table.concat(aliases, ", "))) end end if #formatted_labels > 0 then table.sort(formatted_labels) return ("The following label%s generate%s this category: %s."):format( 				#formatted_labels == 1 and "" or "s", #formatted_labels == 1 and "s" or "", table.concat(formatted_labels, "; ")) end end if self._lang then local desc = self._data.description

desc = self:replace_special_descriptions(desc) if not isChild and desc then if self._data.preceding then desc = self._data.preceding .. "\n\n" .. desc end if self._data.additional then desc = desc .. "\n\n" .. self._data.additional end local labels_msg = get_labels_categorizing if labels_msg then desc = desc .. "\n\n" .. labels_msg end end

return self:substitute_template_specs(desc) else if self._info.label == "all topics" or self._info.label == "all sets" then return "This category applies to content and not to meta material about the Wiki." end

local desc = self._data.umbrella and self._data.umbrella.description or self._data.umbrella_description local has_umbrella_desc = not not desc if not desc then desc = self._data.description if desc then desc = self:replace_special_descriptions(desc) desc = remove_lang_params(desc) desc = desc:gsub("%.$", "") desc = "This category concerns the topic: " .. desc .. "."			 end end if not desc then desc = "Categories concerning " .. self._info.label .. " in various specific languages." end

if not isChild then local preceding = self._data.umbrella and self._data.umbrella.preceding or				not has_umbrella_desc and self._data.preceding local additional = self._data.umbrella and self._data.umbrella.additional or				not has_umbrella_desc and self._data.additional if preceding then desc = remove_lang_params(preceding) .. "\n\n" .. desc end if additional then desc = desc .. "\n\n" .. remove_lang_params(additional) end desc = desc .. "\n\n" local labels_msg = get_labels_categorizing if labels_msg then desc = desc .. "\n\n" .. labels_msg end end desc = self:substitute_template_specs(desc) return desc end end

function Category:getParents local parents = self._data["parents"] local label = self._info.label

if not self._lang and ( label == "all topics" or label == "all sets" ) then return end if not parents or #parents == 0 then return nil end local ret = {} for key, parent in ipairs(parents) do		parent = mw.clone(parent) if type(parent) ~= "table" then parent = {name = parent} end if not parent.sort then -- When defaulting sort key to label, strip 'The ' (e.g. in 'The Matrix', 'The Hunger Games') -- and 'A ' (e.g. in 'A Song of Ice and Fire', 'A Christmas Carol') from label. local stripped_sort = label:match("^[Tt]he (.*)$") if stripped_sort then parent.sort = stripped_sort end if not stripped_sort then stripped_sort = label:match("^[Aa] (.*)$") if stripped_sort then parent.sort = stripped_sort end end if not stripped_sort then parent.sort = label end end if self._lang then parent.sort = self:substitute_template_specs(parent.sort) elseif parent.sort:find("") or parent.sort:find("") or parent.module then return nil end if not self._lang then parent.sort = " " .. parent.sort end if parent.name and parent.name:find("^Category:") then if self._lang then parent.name = self:substitute_template_specs(parent.name) elseif parent.name:find("") or parent.name:find("") or parent.module then return nil end else local pinfo = mw.clone(self._info) pinfo.label = parent.name if parent.module then -- A reference to a category using another category tree module. if not parent.args then error("Missing .args in parent table with module=\"" .. parent.module .. "\" for '" ..						label .. "' topic entry in module '" .. (self._data.module or "unknown") .. "'") end parent.name = require("Module:category tree/" .. parent.module).new(self:substitute_template_specs_in_args(parent.args)) else parent.name = Category.new(pinfo) end end table.insert(ret, parent) end

if self._data.type ~= "toplevel" then local types = self._data.type or "topic" for typ in rgsplit(types, "%s*,%s*") do			local pinfo = mw.clone(self._info) pinfo.label = typ == "topic" and "list of topics" or				typ == "type" and "list of type categories" or				typ == "name" and "list of name categories" or				typ == "set" and "list of sets" or				error(("Invalid type '%s', should be one or more of 'topic', 'set', 'name' or 'type', comma-separated")				:format(types)) table.insert(ret, {name = Category.new(pinfo), sort = (not self._lang and " " or "") .. label}) end end return ret end

function Category:getChildren return nil end

function Category:getUmbrella if not self._lang then return nil end local uinfo = mw.clone(self._info) uinfo.code = nil return Category.new(uinfo) end

function Category:getTOCTemplateName local lang = self._lang local code = lang and lang:getCode or "en" return "Template:" .. code .. "-categoryTOC" end

return export