Module:Category handler: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
rollback again
Tag: Manual revert
Ganaram inukshuk (talk | contribs)
rollback to a different instance
Line 1: Line 1:
-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
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, or table of excluded
-- the page is in the table of excluded namespaces.
-- suffixes (subpages).
-- 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)
-- Get current namespace, as lowercase
local ns_text
local curr_ns = mw.ustring.lower(mw.title.getCurrentTitle().nsText or '')
 
if type(title) == "table" and title.nsText then
-- Build table of suppressed namespaces; start with default list
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


-- Then extend/override list using ns_override, if available
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; if no namespace found, default to false
return namespaces[ns_text] or false
return namespaces[curr_ns] or false
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 title = mw.title.getCurrentTitle()
local title_obj
local pagename = mw.ustring.lower(title.text)


-- Build table of suppressed suffixes, start with default suffixes
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


-- Then append additional suffixes, if available (append, not replace)
-- 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


-- Find and match suffix
-- 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.ustring.lower(mw.text.trim(suffix or '')) -- Also normalize
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, suffixes, is_debug)
function p._category_handler(cats, ns_override, suffixes_override, is_debug)
local cats = cats or {}
    cats = cats or {}
local is_debug = yesno(is_debug, false)
    is_debug = yesno(is_debug, false)
 
-- Test values; should be commented out for normal use
    local frame = mw.getCurrentFrame()
--local cats = cats or {"Abstract MOS patterns", "7-tone scales"}
    local parent_frame = frame:getParent()
--local ns_override = ns_override or { ["module"] = true }
    local parent_title = parent_frame and mw.title.new(parent_frame:getTitle()) or mw.title.getCurrentTitle()
 
-- If in a suppressed namespace/prefix, or in debug mode (suppresses ALL
    -- If parent page is a documentation subpage (e.g. ends with /doc), switch to base page
-- categorization) don't bother
    parent_title = strip_suffix(parent_title, "/doc")
if is_suppressed_namespace(ns_override)  
 
or has_suppressed_suffix(suffixes)
    local suppressed_ns = is_suppressed_namespace(parent_title, ns_override)
or is_debug then
    local suppressed_suffix = has_suppressed_suffix(parent_title, suffixes_override)
return ''
    local is_content = parent_title.isContentPage
end
 
    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


-- Otherwise, categorize
    local result = ''
local result = ''
    for _, cat in ipairs(cats) do
for _, cat in ipairs(cats) do
        cat = mw.text.trim(cat or '')
cat = mw.text.trim(cat or '')
        if cat ~= '' then
if cat ~= '' then
            result = result .. string.format('[[Category:%s]]', cat)
result = result .. string.format('[[Category:%s]]', cat)
        end
end
    end
end


return result
    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:getParent().args
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"   ] or ""
local suffixes_unparsed    = args["suffixes"] or ""
local is_debug = yesno(args["debug"], false) -- Parse debug mode; setting this to TRUE disables all categories
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)
-- These are added in addition to the default list
-- This currently can't force-allow suppressed namespaces as template input,
-- only disallow additional namespaces
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

Module documentation[view] [edit] [history] [purge]
This module may be invoked by templates using its corresponding template Template:Category handler, or used directly from other modules.
Module:Category handler is ready for use. This message indicates that a module is ready for use, or has recently been repaired. This message may be removed once this module has been used on several pages or once it is verified to work as intended.

Details: Functionally complete. Default lists may be adjusted still.

Introspection summary for Module:Category handler 
Functions provided (2)
Line Function Params
130 _category_handler (main) (cats, ns_override, suffixes_override, is_debug)
175 category_handler (invokable) (frame)
Lua modules required (2)
Variable Module Functions used
getArgs Module:Arguments getArgs
yesno Module:Yesno yesno

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