Module:Category handler: Difference between revisions
Jump to navigation
Jump to search
rollback again Tag: Manual revert |
rollback to a different instance |
||
| Line 1: | Line 1: | ||
local getArgs = require("Module:Arguments").getArgs | |||
local yesno = require("Module:Yesno") | local yesno = require("Module:Yesno") | ||
| Line 6: | Line 6: | ||
-- Basic category handler, based on Wikipedia's category handler. It categorizes | -- Basic category handler, based on Wikipedia's category handler. It categorizes | ||
-- pages, given a table of categories as input, and suppresses categorization if | -- pages, given a table of categories as input, and suppresses categorization if | ||
-- the page is in the table of excluded namespaces | -- the page is in the table of excluded namespaces. | ||
-- The most common categorizing templates that have/require complex rules are: | -- The most common categorizing templates that have/require complex rules are: | ||
-- - Infoboxes; these usually shouldn't categorize if they're outside the main | -- - Infoboxes; these usually shouldn't categorize if they're outside the main | ||
| Line 47: | Line 46: | ||
-- Helper function: check if current namespace is excluded | -- Helper function: check if current namespace is excluded | ||
-- Accepts an optional table, containing or overriding other namespaces' rules | -- Accepts an optional table, containing or overriding other namespaces' rules | ||
local function is_suppressed_namespace(ns_override) | local function is_suppressed_namespace(title, ns_override) | ||
local ns_text | |||
if type(title) == "table" and title.nsText then | |||
ns_text = mw.ustring.lower(title.nsText or '') | |||
elseif type(title) == "string" then | |||
ns_text = mw.ustring.lower(title) | |||
else | |||
ns_text = mw.ustring.lower(mw.title.getCurrentTitle().nsText or '') | |||
end | |||
local namespaces = {} | local namespaces = {} | ||
for k, v in pairs(DEFAULT_SUPPRESSED_NAMESPACES) do | for k, v in pairs(DEFAULT_SUPPRESSED_NAMESPACES) do | ||
| Line 57: | Line 62: | ||
end | end | ||
if type(ns_override) == "table" then | if type(ns_override) == "table" then | ||
for k, v in pairs(ns_override) do | for k, v in pairs(ns_override) do | ||
| Line 64: | Line 68: | ||
end | end | ||
return namespaces[ns_text] or false | |||
return namespaces[ | |||
end | end | ||
| Line 72: | Line 75: | ||
-- Accepts an opiontal table, containing additional suffixes to suppress | -- Accepts an opiontal table, containing additional suffixes to suppress | ||
-- Suffix and custom suffixes are lowercased to guarantee matching | -- Suffix and custom suffixes are lowercased to guarantee matching | ||
local function has_suppressed_suffix(suffixes_override) | local function has_suppressed_suffix(title, suffixes_override) | ||
local | local title_obj | ||
if type(title) == "table" and title.text then | |||
title_obj = title | |||
elseif type(title) == "string" then | |||
title_obj = mw.title.new(title) | |||
else | |||
title_obj = mw.title.getCurrentTitle() | |||
end | |||
local pagename = mw.ustring.lower(title_obj.text or '') | |||
local suffixes = {} | local suffixes = {} | ||
-- Include default suppressed suffixes | |||
for _, suffix in ipairs(DEFAULT_SUPPRESSED_SUFFIXES) do | for _, suffix in ipairs(DEFAULT_SUPPRESSED_SUFFIXES) do | ||
table.insert(suffixes, suffix) | table.insert(suffixes, suffix) | ||
end | end | ||
-- | -- Add custom overrides | ||
if type(suffixes_override) == "table" then | if type(suffixes_override) == "table" then | ||
for _, suffix in ipairs(suffixes_override) do | for _, suffix in ipairs(suffixes_override) do | ||
| Line 89: | Line 101: | ||
end | end | ||
-- | -- Check if the page name ends with any of the suffixes (as subpages) | ||
for _, suffix in ipairs(suffixes) do | for _, suffix in ipairs(suffixes) do | ||
suffix = mw. | suffix = mw.text.trim(mw.ustring.lower(suffix or '')) | ||
if suffix ~= '' then | if suffix ~= '' then | ||
local pattern = '/' .. mw.ustring.gsub(suffix, '([%^%$%(%)%%%.%[%]%*%+%-%?])', '%%%1') .. '$' | local pattern = '/' .. mw.ustring.gsub(suffix, '([%^%$%(%)%%%.%[%]%*%+%-%?])', '%%%1') .. '$' | ||
| Line 101: | Line 113: | ||
return false | return false | ||
end | |||
-- Helper function: strips suffix from title | |||
local function strip_suffix(title, suffix) | |||
local text = title.text | |||
if mw.ustring.sub(text, -mw.ustring.len(suffix)) == suffix then | |||
return mw.title.new(mw.ustring.sub(text, 1, -mw.ustring.len(suffix)-1), title.ns) | |||
end | |||
return title | |||
end | end | ||
| Line 107: | Line 128: | ||
-- Disallows categories if it's in a suppressed namespace or the page has a | -- Disallows categories if it's in a suppressed namespace or the page has a | ||
-- suppressed suffix (subpage) | -- suppressed suffix (subpage) | ||
function p._category_handler(cats, ns_override, | function p._category_handler(cats, ns_override, suffixes_override, is_debug) | ||
cats = cats or {} | |||
is_debug = yesno(is_debug, false) | |||
local frame = mw.getCurrentFrame() | |||
local parent_frame = frame:getParent() | |||
local parent_title = parent_frame and mw.title.new(parent_frame:getTitle()) or mw.title.getCurrentTitle() | |||
-- If parent page is a documentation subpage (e.g. ends with /doc), switch to base page | |||
parent_title = strip_suffix(parent_title, "/doc") | |||
local suppressed_ns = is_suppressed_namespace(parent_title, ns_override) | |||
local suppressed_suffix = has_suppressed_suffix(parent_title, suffixes_override) | |||
local is_content = parent_title.isContentPage | |||
if is_debug or suppressed_ns or suppressed_suffix or not is_content then | |||
local reason = is_debug and "debug mode" | |||
or (suppressed_ns and "suppressed namespace") | |||
or (suppressed_suffix and "suppressed suffix") | |||
or (not is_content and "non-content page") | |||
or "unknown" | |||
-- Return a debug comment explaining why categorization was suppressed | |||
return string.format( | |||
"<!-- categorization suppressed: %s; is_debug=%s; suppressed_ns=%s; suppressed_suffix=%s; is_content=%s -->", | |||
reason, | |||
tostring(is_debug), | |||
tostring(suppressed_ns), | |||
tostring(suppressed_suffix), | |||
tostring(is_content) | |||
) | |||
end | |||
local result = '' | |||
for _, cat in ipairs(cats) do | |||
cat = mw.text.trim(cat or '') | |||
if cat ~= '' then | |||
result = result .. string.format('[[Category:%s]]', cat) | |||
end | |||
end | |||
return result | |||
end | end | ||
-- Wrapper for templates calling via #invoke | -- Wrapper for templates calling via #invoke | ||
function p.category_handler(frame) | function p.category_handler(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local cats_unparsed = args["categories" ] or "" | local cats_unparsed = args["categories"] or "" | ||
local excluded_ns_unparsed = args["excluded_ns"] or "" | local excluded_ns_unparsed = args["excluded_ns"] or "" | ||
local suffixes_unparsed = args["suffixes" | local suffixes_unparsed = args["suffixes"] or "" | ||
local is_debug = yesno(args["debug"], false) | local is_debug = yesno(args["debug"], false) | ||
-- Parse categories | -- Parse categories | ||
| Line 153: | Line 189: | ||
end | end | ||
-- Parse excluded namespaces | -- Parse excluded namespaces (added/overrides default) | ||
local ns_override = {} | local ns_override = {} | ||
for ns in mw.text.gsplit(excluded_ns_unparsed, "[;\n]") do | for ns in mw.text.gsplit(excluded_ns_unparsed, "[;\n]") do | ||
Revision as of 08:50, 21 October 2025
- This module may be invoked by templates using its corresponding template Template:Category handler, or used directly from other modules.
| Introspection summary for Module:Category handler | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
| ||||||||||||||||||
No function descriptions were provided. The Lua code may have further information.
local getArgs = require("Module:Arguments").getArgs
local yesno = require("Module:Yesno")
local p = {}
-- Basic category handler, based on Wikipedia's category handler. It categorizes
-- pages, given a table of categories as input, and suppresses categorization if
-- the page is in the table of excluded namespaces.
-- The most common categorizing templates that have/require complex rules are:
-- - Infoboxes; these usually shouldn't categorize if they're outside the main
-- namespace.
-- - Certain mboxes; some mboxes categorize certain pages, but they shouldn't
-- categorize their own documentation.
-- Default list of namespaces in which to suppress categorization. Most
-- categorizing templates are expected to be placed in main, template, or
-- module; those that don't are likely special-use templates (like idiosyncratic
-- and editable user page) which likely won't need this module, or templates
-- with very basic categorization rules for which this is overkill.
-- Adjust as needed!
local DEFAULT_SUPPRESSED_NAMESPACES = {
["talk"] = true,
["user"] = true,
["user talk"] = true,
["file talk"] = true,
["mediawiki talk"] = true,
["template talk"] = true,
["help"] = true,
["help talk"] = true,
["category talk"] = true,
["module talk"] = true,
["xenharmonic wiki"] = true,
["xenharmonic wiki talk"] = true,
["media"] = true,
}
-- Inversely, the following namespaces are unsuppressed: main, file, mediawiki,
-- template, category, module
-- Default list of page suffixes in which to suppress categorization.
-- Adjust as needed!
local DEFAULT_SUPPRESSED_SUFFIXES = {
"doc",
"sandbox",
}
-- Helper function: check if current namespace is excluded
-- Accepts an optional table, containing or overriding other namespaces' rules
local function is_suppressed_namespace(title, ns_override)
local ns_text
if type(title) == "table" and title.nsText then
ns_text = mw.ustring.lower(title.nsText or '')
elseif type(title) == "string" then
ns_text = mw.ustring.lower(title)
else
ns_text = mw.ustring.lower(mw.title.getCurrentTitle().nsText or '')
end
local namespaces = {}
for k, v in pairs(DEFAULT_SUPPRESSED_NAMESPACES) do
namespaces[k] = v
end
if type(ns_override) == "table" then
for k, v in pairs(ns_override) do
namespaces[k] = v
end
end
return namespaces[ns_text] or false
end
-- Helper function
-- Checks whether the page title ends in a suppressed suffix
-- Accepts an opiontal table, containing additional suffixes to suppress
-- Suffix and custom suffixes are lowercased to guarantee matching
local function has_suppressed_suffix(title, suffixes_override)
local title_obj
if type(title) == "table" and title.text then
title_obj = title
elseif type(title) == "string" then
title_obj = mw.title.new(title)
else
title_obj = mw.title.getCurrentTitle()
end
local pagename = mw.ustring.lower(title_obj.text or '')
local suffixes = {}
-- Include default suppressed suffixes
for _, suffix in ipairs(DEFAULT_SUPPRESSED_SUFFIXES) do
table.insert(suffixes, suffix)
end
-- Add custom overrides
if type(suffixes_override) == "table" then
for _, suffix in ipairs(suffixes_override) do
table.insert(suffixes, suffix)
end
end
-- Check if the page name ends with any of the suffixes (as subpages)
for _, suffix in ipairs(suffixes) do
suffix = mw.text.trim(mw.ustring.lower(suffix or ''))
if suffix ~= '' then
local pattern = '/' .. mw.ustring.gsub(suffix, '([%^%$%(%)%%%.%[%]%*%+%-%?])', '%%%1') .. '$'
if mw.ustring.match(pagename, pattern) then
return true
end
end
end
return false
end
-- Helper function: strips suffix from title
local function strip_suffix(title, suffix)
local text = title.text
if mw.ustring.sub(text, -mw.ustring.len(suffix)) == suffix then
return mw.title.new(mw.ustring.sub(text, 1, -mw.ustring.len(suffix)-1), title.ns)
end
return title
end
-- "Main" function; can be called by other modules
-- Categorizes a page, given a table of categories
-- Disallows categories if it's in a suppressed namespace or the page has a
-- suppressed suffix (subpage)
function p._category_handler(cats, ns_override, suffixes_override, is_debug)
cats = cats or {}
is_debug = yesno(is_debug, false)
local frame = mw.getCurrentFrame()
local parent_frame = frame:getParent()
local parent_title = parent_frame and mw.title.new(parent_frame:getTitle()) or mw.title.getCurrentTitle()
-- If parent page is a documentation subpage (e.g. ends with /doc), switch to base page
parent_title = strip_suffix(parent_title, "/doc")
local suppressed_ns = is_suppressed_namespace(parent_title, ns_override)
local suppressed_suffix = has_suppressed_suffix(parent_title, suffixes_override)
local is_content = parent_title.isContentPage
if is_debug or suppressed_ns or suppressed_suffix or not is_content then
local reason = is_debug and "debug mode"
or (suppressed_ns and "suppressed namespace")
or (suppressed_suffix and "suppressed suffix")
or (not is_content and "non-content page")
or "unknown"
-- Return a debug comment explaining why categorization was suppressed
return string.format(
"<!-- categorization suppressed: %s; is_debug=%s; suppressed_ns=%s; suppressed_suffix=%s; is_content=%s -->",
reason,
tostring(is_debug),
tostring(suppressed_ns),
tostring(suppressed_suffix),
tostring(is_content)
)
end
local result = ''
for _, cat in ipairs(cats) do
cat = mw.text.trim(cat or '')
if cat ~= '' then
result = result .. string.format('[[Category:%s]]', cat)
end
end
return result
end
-- Wrapper for templates calling via #invoke
function p.category_handler(frame)
local args = getArgs(frame)
local cats_unparsed = args["categories"] or ""
local excluded_ns_unparsed = args["excluded_ns"] or ""
local suffixes_unparsed = args["suffixes"] or ""
local is_debug = yesno(args["debug"], false)
-- Parse categories
local cats = {}
for cat in mw.text.gsplit(cats_unparsed, "[,\n]") do
cat = mw.text.trim(cat)
if cat ~= "" then
table.insert(cats, cat)
end
end
-- Parse excluded namespaces (added/overrides default)
local ns_override = {}
for ns in mw.text.gsplit(excluded_ns_unparsed, "[;\n]") do
ns = mw.text.trim(ns)
if ns ~= "" then
ns_override[ns] = true
end
end
-- Parse excluded suffixes
local suffixes = {}
for suffix in mw.text.gsplit(suffixes_unparsed, "[;\n]") do
suffix = mw.text.trim(suffix)
if suffix ~= "" then
table.insert(suffixes, suffix)
end
end
return p._category_handler(cats, ns_override, suffixes, is_debug)
end
return p