Module:Category handler: Difference between revisions

From WIDEVERSE Wiki
Jump to navigation Jump to search
m (1 revision imported)
m (1 revision imported)
 
(5 intermediate revisions by 3 users not shown)
Line 1: Line 1:
--------------------------------------------------------------------------------
--[=[ <pre>
--                                                                            --
-- Implements [Template:Ctg]
--                              CATEGORY HANDLER                              --
-- Sorts pages into a category more appropriately than pagename alone
--                                                                            --
-- Default and custom rules are outlined at [[Template:Category handler/doc]]
--      This module implements the {{category handler}} template in Lua,      --
--]=]
--      with a few improvements: all namespaces and all namespace aliases    --
--      are supported, and namespace names are detected automatically for    --
--      the local wiki. This module requires [[Module:Namespace detect]]      --
--     and [[Module:Yesno]] to be available on the local wiki. It can be    --
--     configured for different wikis by altering the values in              --
--     [[Module:Category handler/config]], and pages can be blacklisted      --
--     from categorisation by using [[Module:Category handler/blacklist]].  --
--                                                                            --
--------------------------------------------------------------------------------


-- Load required modules
local p = {}
local yesno = require('Module:Yesno')


-- Lazily load things we don't always need
local ucf = require('Module:Paramtest').ucfirst
local mShared, mappings


local p = {}
local curpage = mw.title.getCurrentTitle()


--------------------------------------------------------------------------------
function p.main(frame)
-- Helper functions
local ns = curpage.namespace
--------------------------------------------------------------------------------


local function trimWhitespace(s, removeBlanks)
-- Just don't bother unless we're in content namespaces
if type(s) ~= 'string' then
if not (ns == 0 or ns == 120) then
return s
return ''
end
end
s = s:match('^%s*(.-)%s*$')
if removeBlanks then
if s ~= '' then
return s
else
return nil
end
else
return s
end
end


--------------------------------------------------------------------------------
local args = frame:getParent().args
-- CategoryHandler class
local cats = {}
--------------------------------------------------------------------------------
 
for _, v in ipairs(args) do
local cat_x = {}
 
-- Replace underscores with spaces
-- Condense and trim white-space; remove new lines (just in case)
v = mw.text.trim(v)
:gsub('[_%s]+',' ')
:gsub('\n','')
 
-- Snip category name now, up to the index of the first set of two colons
-- If no colons, just use the whole string
local cat_n = (v:match('^([^:]+)::') or v)
:gsub('[Cc]ategory:%s*','')
 
-- Set category name to the name just snipped
cat_x.name = cat_n
 
-- Page title includes matched text
-- Matched text is defined by ::ifmatches[text]
-- or if empty, defaults to category name
if v:find('::ifmatches') then
-- Look for brackets used as delimiters and capture them all
local match_set = v:match('::ifmatches(%[[^:]+%])')


local CategoryHandler = {}
-- If none are found, use the pagename
CategoryHandler.__index = CategoryHandler
if not match_set then
cat_x.ifmatch = {cat_n}
else
cat_x.ifmatch = {}
-- Split match into table, delimited by brackets
match_set = mw.text.split(match_set,'[%]%[]+')


function CategoryHandler.new(data, args)
-- Add to match table; only if not blank
local obj = setmetatable({ _data = data, _args = args }, CategoryHandler)
-- An empty string is created by "[" at the beginning
for _, w in ipairs(match_set) do
-- Set the title object
if w:find('%S') then
do
table.insert(cat_x.ifmatch,w)
local pagename = obj:parameter('demopage')
end
local success, titleObj
end
if pagename then
success, titleObj = pcall(mw.title.new, pagename)
end
if success and titleObj then
obj.title = titleObj
if titleObj == mw.title.getCurrentTitle() then
obj._usesCurrentTitle = true
end
end
else
obj.title = mw.title.getCurrentTitle()
obj._usesCurrentTitle = true
end
end


-- Set suppression parameter values
-- Iterate through and escape all metacharacters
for _, key in ipairs{'nocat', 'categories'} do
-- Prevents errors when they're passed to string.match() below
local value = obj:parameter(key)
-- Make everything lowercase
value = trimWhitespace(value, true)
for i, w in ipairs(cat_x.ifmatch) do
obj['_' .. key] = yesno(value)
cat_x.ifmatch[i] = w:gsub(
end
-- Chars: - ^ $ * ( ) + ?
do
'([%-^$*()+?])',
local subpage = obj:parameter('subpage')
'%%%1'):lower()
local category2 = obj:parameter('category2')
if type(subpage) == 'string' then
subpage = mw.ustring.lower(subpage)
end
if type(category2) == 'string' then
subpage = mw.ustring.lower(category2)
end
obj._subpage = trimWhitespace(subpage, true)
obj._category2 = trimWhitespace(category2) -- don't remove blank values
end
return obj
end
 
function CategoryHandler:parameter(key)
local parameterNames = self._data.parameters[key]
local pntype = type(parameterNames)
if pntype == 'string' or pntype == 'number' then
return self._args[parameterNames]
elseif pntype == 'table' then
for _, name in ipairs(parameterNames) do
local value = self._args[name]
if value ~= nil then
return value
end
end
end
end
return nil
else
error(string.format(
'invalid config key "%s"',
tostring(key)
), 2)
end
end


function CategoryHandler:isSuppressedByArguments()
-- Text to strip from the front of the sort
return
-- Can be user defined with ::remove[text]
-- See if a category suppression argument has been set.
-- Defaults to category name exactly
self._nocat == true
-- Escape metacharacters to prevent errors when they're passed to string.match() below
or self._categories == false
cat_x.trim = string.gsub(
or (
v:match('::remove%[%s*([^]:]+)%s*%]') or
self._category2
cat_n,
and self._category2 ~= self._data.category2Yes
-- Chars: - ^ $ * ( ) + ?
and self._category2 ~= self._data.category2Negative
'([%-^$*()+?])',
)
'%%%1')


-- Check whether we are on a subpage, and see if categories are
-- Add category and its rules into the list
-- suppressed based on our subpage status.
table.insert(cats,cat_x)
or self._subpage == self._data.subpageNo and self.title.isSubpage
end
or self._subpage == self._data.subpageOnly and not self.title.isSubpage
end


function CategoryHandler:shouldSkipBlacklistCheck()
return p._main(cats)
-- Check whether the category suppression arguments indicate we
-- should skip the blacklist check.
return self._nocat == false
or self._categories == true
or self._category2 == self._data.category2Yes
end
end


function CategoryHandler:matchesBlacklist()
function p._main(cat_list)
if self._usesCurrentTitle then
-- Pagename, exactly, in all lowercase, and escaped (used for matching)
return self._data.currentTitleMatchesBlacklist
local pagename = curpage.text
else
local pagelc = pagename:lower()
mShared = mShared or require('Module:Category handler/shared')
local pageesc = pagelc:gsub(
return mShared.matchesBlacklist(
-- Chars: - ^ $ * ( ) + ?
self.title.prefixedText,
'([%-^$*()+?])',
mw.loadData('Module:Category handler/blacklist')
'%%%1')
)
-- Return table
end
local ctg = {}
end


function CategoryHandler:isSuppressed()
for _, v in ipairs(cat_list) do
-- Find if categories are suppressed by either the arguments or by
-- Category name and in lowercase
-- matching the blacklist.
local cn = v.name
return self:isSuppressedByArguments()
local cnl = cn:lower()
or not self:shouldSkipBlacklistCheck() and self:matchesBlacklist()
-- Text to remove
end
local rmv = v.trim:lower()


function CategoryHandler:getNamespaceParameters()
-- Little thing that checks pagename against everything in the matches table
if self._usesCurrentTitle then
-- If there's no table, keep as false (it won't matter)
return self._data.currentTitleNamespaceParameters
local pagematches = false
else
if v.ifmatch then
if not mappings then
for _, w in ipairs(v.ifmatch) do
mShared = mShared or require('Module:Category handler/shared')
-- Look for exact match, and with faux-singular too
mappings = mShared.getParamMappings(true) -- gets mappings with mw.loadData
if pagelc:find(w) or
(w:find('s$') and
pagelc:find(w:match('(.*)s$')))
then
pagematches = true
end
end
end
end
return mShared.getNamespaceParameters(
self.title,
mappings
)
end
end


function CategoryHandler:namespaceParametersExist()
-- Create a second string that counts as the singular of the text to remove
-- Find whether any namespace parameters have been specified.
-- If it works as a singular, and the page name is singular, then use it too
-- We use the order "all" --> namespace params --> "other" as this is what
-- Otherwise, just make it the same as rmv
-- the old template did.
local rmvpl = rmv
if self:parameter('all') then
if rmv:find('s$') then
return true
rmvpl = rmv:match('(.*)s$')
end
if pagelc:find('^'..rmvpl) and
if not mappings then
(not pagelc:find('^'..rmv))
mShared = mShared or require('Module:Category handler/shared')
then
mappings = mShared.getParamMappings(true) -- gets mappings with mw.loadData
-- Nothing
end
else
for ns, params in pairs(mappings) do
rmvpl = rmv
for i, param in ipairs(params) do
if self._args[param] then
return true
end
end
end
end
end
if self:parameter('other') then
return true
end
return false
end


function CategoryHandler:getCategories()
-- If v.ifmatch is not specified or
local params = self:getNamespaceParameters()
-- It is and the pattern matches any part of the pagename
local nsCategory
-- Continue to add categories
for i, param in ipairs(params) do
if (not v.ifmatch) or
local value = self._args[param]
(v.ifmatch and pagematches)
if value ~= nil then
then
nsCategory = value
-- If the pagename matches category name exactly
break
-- Or either is a simple plural of the other
end
-- Or the text to remove exactly
end
-- Sort to front
if nsCategory ~= nil or self:namespaceParametersExist() then
if pagelc:find('^'..cnl..'$') or
-- Namespace parameters exist - advanced usage.
cnl:find('^'..pageesc..'s$') or
if nsCategory == nil then
pagelc:find('^'..cnl..'s$') or
nsCategory = self:parameter('other')
pagelc:find('^'..rmv..'$')
end
then
local ret = {self:parameter('all')}
table.insert(ctg,string.format('[[Category:%s| ]]',cn))
local numParam = tonumber(nsCategory)
 
if numParam and numParam >= 1 and math.floor(numParam) == numParam then
-- If the pagename begins with the category name
-- nsCategory is an integer
-- Sort with beginning remove
ret[#ret + 1] = self._args[numParam]
elseif pagelc:find('^'..rmv) or
else
pagelc:find('^'..rmvpl)
ret[#ret + 1] = nsCategory
then
end
-- Offset by an extra character if it's not plural
if #ret < 1 then
-- Or the page starts with plural
return nil
if rmvpl == rmv then
else
offset = 1
return table.concat(ret)
else
end
offset = 0
elseif self._data.defaultNamespaces[self.title.namespace] then
end
-- Namespace parameters don't exist, simple usage.
return self._args[1]
end
return nil
end


--------------------------------------------------------------------------------
-- Unescape metacharacters for proper length
-- Exports
local key = pagename:sub( #(rmv:gsub('%%','')) + offset )
--------------------------------------------------------------------------------


local p = {}
key = ucf(mw.text.trim(key))


function p._exportClasses()
-- Remove punctuation from start if leftover
-- Used for testing purposes.
-- Such as "/" leftover on subpages
return {
-- Or "(" for disambiguated pages
CategoryHandler = CategoryHandler
if key:find('^%p') then
}
key = ucf(key:sub(2))
end
-- Just in case, remove "s" preceding punctuation
elseif key:find('^S%p') then
key = ucf(key:sub(3))
end


function p._main(args, data)
table.insert(ctg,string.format('[[Category:%s|%s]]',cn,key))
data = data or mw.loadData('Module:Category handler/data')
local handler = CategoryHandler.new(data, args)
if handler:isSuppressed() then
return nil
end
return handler:getCategories()
end


function p.main(frame, data)
-- Everything else just gets the category added plainly
data = data or mw.loadData('Module:Category handler/data')
local args = require('Module:Arguments').getArgs(frame, {
wrappers = data.wrappers,
valueFunc = function (k, v)
v = trimWhitespace(v)
if type(k) == 'number' then
if v ~= '' then
return v
else
return nil
end
else
else
return v
table.insert(ctg,string.format('[[Category:%s]]',cn))
end
end
end
end
})
end
return p._main(args, data)
 
return table.concat(ctg)
end
end


return p
return p

Latest revision as of 19:35, 7 November 2021

This module does not have any documentation. Please consider adding documentation at Module:Category handler/doc. [edit]
Module:Category handler requires Module:Paramtest.
Module:Category handler is required by Module:Userbox.

--[=[ <pre>
-- Implements [Template:Ctg]
-- Sorts pages into a category more appropriately than pagename alone
-- Default and custom rules are outlined at [[Template:Category handler/doc]]
--]=]

local p = {}

local ucf = require('Module:Paramtest').ucfirst

local curpage = mw.title.getCurrentTitle()

function p.main(frame)
	local ns = curpage.namespace

	-- Just don't bother unless we're in content namespaces
	if not (ns == 0 or ns == 120) then
		return ''
	end

	local args = frame:getParent().args 
	local cats = {}

	for _, v in ipairs(args) do
		local cat_x = {}

		-- Replace underscores with spaces
		-- Condense and trim white-space; remove new lines (just in case)
		v = mw.text.trim(v)
			:gsub('[_%s]+',' ')
			:gsub('\n','')

		-- Snip category name now, up to the index of the first set of two colons
		-- If no colons, just use the whole string
		local cat_n = (v:match('^([^:]+)::') or v)
				:gsub('[Cc]ategory:%s*','')

		-- Set category name to the name just snipped
		cat_x.name = cat_n

		-- Page title includes matched text
		-- Matched text is defined by ::ifmatches[text]
		-- or if empty, defaults to category name
		if v:find('::ifmatches') then
			-- Look for brackets used as delimiters and capture them all
			local match_set = v:match('::ifmatches(%[[^:]+%])')

			-- If none are found, use the pagename
			if not match_set then
				cat_x.ifmatch = {cat_n}
			else
				cat_x.ifmatch = {}
				-- Split match into table, delimited by brackets
				match_set = mw.text.split(match_set,'[%]%[]+')

				-- Add to match table; only if not blank
				-- An empty string is created by "[" at the beginning
				for _, w in ipairs(match_set) do
					if w:find('%S') then
						table.insert(cat_x.ifmatch,w)
					end
				end
			end

			-- Iterate through and escape all metacharacters
			-- Prevents errors when they're passed to string.match() below
			-- Make everything lowercase
			for i, w in ipairs(cat_x.ifmatch) do
				cat_x.ifmatch[i] = w:gsub(
						-- Chars: - ^ $ * ( ) + ?
						'([%-^$*()+?])',
						'%%%1'):lower()
			end
		end

		-- Text to strip from the front of the sort
		-- Can be user defined with ::remove[text]
		-- Defaults to category name exactly
		-- Escape metacharacters to prevent errors when they're passed to string.match() below
		cat_x.trim = string.gsub(
				v:match('::remove%[%s*([^]:]+)%s*%]') or
					cat_n,
					-- Chars: - ^ $ * ( ) + ?
					'([%-^$*()+?])',
					'%%%1')

		-- Add category and its rules into the list
		table.insert(cats,cat_x)
	end

	return p._main(cats)
end

function p._main(cat_list)
	-- Pagename, exactly, in all lowercase, and escaped (used for matching)
	local pagename = curpage.text
	local pagelc = pagename:lower()
	local pageesc = pagelc:gsub(
				-- Chars: - ^ $ * ( ) + ?
				'([%-^$*()+?])',
				'%%%1')
	-- Return table
	local ctg = {}

	for _, v in ipairs(cat_list) do
		-- Category name and in lowercase
		local cn = v.name
		local cnl = cn:lower()
		-- Text to remove
		local rmv = v.trim:lower()

		-- Little thing that checks pagename against everything in the matches table
		-- If there's no table, keep as false (it won't matter)
		local pagematches = false
		if v.ifmatch then
			for _, w in ipairs(v.ifmatch) do
				-- Look for exact match, and with faux-singular too
				if pagelc:find(w) or
				(w:find('s$') and 
					pagelc:find(w:match('(.*)s$')))
				then
					pagematches = true
				end
			end
		end

		-- Create a second string that counts as the singular of the text to remove
		-- If it works as a singular, and the page name is singular, then use it too
		-- Otherwise, just make it the same as rmv
		local rmvpl = rmv
		if rmv:find('s$') then
			rmvpl = rmv:match('(.*)s$')
			if pagelc:find('^'..rmvpl) and
			(not pagelc:find('^'..rmv))
			then
				-- Nothing
			else
				rmvpl = rmv
			end
		end

		-- If v.ifmatch is not specified or
		-- It is and the pattern matches any part of the pagename
		-- Continue to add categories
		if (not v.ifmatch) or
			(v.ifmatch and pagematches)
		then
			-- If the pagename matches category name exactly
			-- Or either is a simple plural of the other
			-- Or the text to remove exactly
			-- Sort to front
			if pagelc:find('^'..cnl..'$') or
				cnl:find('^'..pageesc..'s$') or
				pagelc:find('^'..cnl..'s$') or
				pagelc:find('^'..rmv..'$')
			then
				table.insert(ctg,string.format('[[Category:%s| ]]',cn))

			-- If the pagename begins with the category name
			-- Sort with beginning remove
			elseif pagelc:find('^'..rmv) or 
				pagelc:find('^'..rmvpl)
			then
				-- Offset by an extra character if it's not plural
				-- Or the page starts with plural
				if rmvpl == rmv then
					offset = 1
				else
					offset = 0
				end

				-- Unescape metacharacters for proper length
				local key = pagename:sub( #(rmv:gsub('%%','')) + offset )

				key = ucf(mw.text.trim(key))

				-- Remove punctuation from start if leftover
				-- Such as "/" leftover on subpages
				-- Or "(" for disambiguated pages
				if key:find('^%p') then
					key = ucf(key:sub(2))
				-- Just in case, remove "s" preceding punctuation
				elseif key:find('^S%p') then
					key = ucf(key:sub(3))
				end

				table.insert(ctg,string.format('[[Category:%s|%s]]',cn,key))

			-- Everything else just gets the category added plainly
			else
				table.insert(ctg,string.format('[[Category:%s]]',cn))
			end
		end
	end

	return table.concat(ctg)
end

return p