模組:Auto cat

本模块用于实现{{auto cat}}


local export = {}
local mzh = require("Module:Zh")

-- 簡轉繁
local function st(title)
	local script = mzh.ts_determ(title)
	if script == 'trad' then return title end
	require("Module:debug").track("auto cat/簡體字分類名")
	return mzh.st(title):gsub('裡', '里')
end

-- Used in multiple places; create a variable for ease in testing.
local poscatboiler_submodule = "poscatboiler"


local function splitLabelLang(titleObject)
	local getByCanonicalName = require("Module:languages").getByCanonicalName
	local title = titleObject.text
	local lang, canonicalName
	-- 先嘗試當做topic處理
	local words = mw.text.split(titleObject.text, " ")
	for i = #words - 1, 1, -1 do  -- 雖然可能漢語語言名沒有空格,但是為了健壯性當做有來處理
		canonicalName = table.concat(words, " ", 1, i)
		lang = getByCanonicalName(canonicalName)
		if lang then
			return " " .. table.concat(words, " ", i + 1, #words), lang
		end
	end
	
	-- 找不到就按以前的方法處理
	local preLabel, posLabel = mw.ustring.match(title, "^(.+[^羅]的)(.+)$")
	preLabel = preLabel or ''
	posLabel = posLabel or title
	-- Progressively remove characters from the end of the category name until it matches a language.
	for i = mw.ustring.len(posLabel), 1, -1 do
		canonicalName = mw.ustring.sub(posLabel, 1, i)
		lang = getByCanonicalName(canonicalName)
		if lang then
			posLabel = posLabel:sub(#canonicalName + 1)
			break
		end
	end
	
	return preLabel .. posLabel, lang
end


-- Add the arguments in `source` to those in `receiver`, offsetting numeric arguments by `offset`.
local function add_args(receiver, source, offset)
	for k, v in pairs(source) do
		if type(k) == "number" then
			receiver[k + offset] = v
		else
			receiver[k] = v
		end
	end
	return receiver
end


-- List of handler functions that try to match the page name.
-- A handler should return a table of template title plus arguments
-- that is passed to frame:expandTemplate.
-- If a handler does not recognise the page name, it should return nil.
-- Note that the order of functions matters!

local handlers = {}

local function add_handler(func)
	table.insert(handlers, func)
end


-- Rhyme cat
add_handler(function(titleObject)
	if not titleObject.text:find("韻部/.*%-$") then
		return nil
	end
	return {title = "rhyme cat"}
end)


-- Letter names
add_handler(function(titleObject)
	if not titleObject.text:find("letter names$") then
		return nil
	end
	
	local langCode = titleObject.text:match("^([^:]+):")
	local lang, cat
	
	if langCode then
		lang = require("Module:languages").getByCode(langCode) or error('The language code "' .. langCode .. '" is not valid.')
		cat = titleObject.text:match(":(.+)$")
	else
		cat = titleObject.text
	end
	
	return {title = "topic cat", args = {lang and lang:getCode() or nil, cat}}
end)


-- letter cat
add_handler(function(titleObject)
	-- Only recognize cases consisting of an uppercase letter followed by the
	-- corresponding lowercase letter, either as the entire category name or
	-- followed by a colon (for cases like [[Category:Gg: ⠛]]). Cases that
	-- don't fit this profile (e.g. for Turkish [[Category:İi]] and
	-- [[Category:Iı]]) need to call {{letter cat}} directly. Formerly this
	-- handler was much less restrictive and would fire on categories named
	-- [[Category:zh:]], [[Category:RFQ]], etc.
	local upper, lower = mw.ustring.match(titleObject.text, "^(%u)(%l)%f[:%z]")
	if not upper or mw.ustring.upper(lower) ~= upper then
		return nil
	end

	return {title = "letter cat"}
end)


-- poscatboiler lang-specific and topical categories
add_handler(function(titleObject, args)
	local label, lang = splitLabelLang(titleObject)
	if lang and label ~= '' then
		if label:match("^ ") then
			return {title = "topic cat", args = {lang:getCode(), label:sub(2)}}
		end
		local script, baseLabel = label:match("以(.-)書寫的(.+)$")
		if script and baseLabel ~= "terms" then
			local scriptObj = require("Module:scripts").getByCanonicalName(script)
			if scriptObj then
				return {title = poscatboiler_submodule, args = add_args({lang:getCode(), baseLabel, scriptObj:getCode()}, args, 3)}, true
			end
		end
		return {title = poscatboiler_submodule, args = add_args({lang:getCode(), label}, args, 3)}, true
	end
end)


-- topic cat
add_handler(function(titleObject)
	return {title = "topic cat", args = {nil, titleObject.text}}
end)


-- poscatboiler raw handlers
add_handler(function(titleObject, args)
	local args = add_args({nil, titleObject.text}, args, 3)
	args.raw = true
	return {
		title = poscatboiler_submodule,
		args = args,
	}, true
end)


-- poscatboiler umbrella handlers
add_handler(function(titleObject, args)
	local args = add_args({nil, titleObject.text}, args, 3)
	return {
		title = poscatboiler_submodule,
		args = args,
	}, true
end)


function export.show(frame)
	local args = frame:getParent().args
	local tempTitleObject = mw.title.getCurrentTitle()
	local titleObject = mw.title.new(st(tempTitleObject.prefixedText))
	local titleObjects = {mw.title.new(tempTitleObject.prefixedText), mw.title.new(st(tempTitleObject.prefixedText))}
	
	if titleObject.nsText == "Template" then
		return "(本模板應該用於Category:命名空間頁面。)"
	elseif titleObject.nsText ~= "Category" then
		error("本模板/模組只能用於Category:命名空間頁面。")
	end

	local function extra_args_error(templateObject)
		local numargstext = {}
		local argstext = {}
		local maxargnum = 0
		for k, v in pairs(templateObject.args) do
			if type(v) == "number" and v > maxargnum then
				maxargnum = v
			else
				table.insert(numargstext, "|" .. k .. "=" .. v)
			end
		end
		for i = 1, maxargnum do
			local v = templateObject.args[i]
			if v == nil then
				v = "(nil)"
			elseif v == true then
				v = "(true)"
			elseif v == false then
				v = "(false)"
			end
			table.insert(argstext, "|" .. v)
		end
		error("Extra arguments to {{auto cat}} not allowed for this category (recognized as {{[[Template:" ..
			templateObject.title .. "|" .. templateObject.title .. "]]" .. numargstext .. argstext .. "}}")
	end

	local first_error_templateObject, first_error_args_handled, first_error_cattext

	-- Go through each handler in turn. If a handler doesn't recognize the format of the
	-- category, it will return nil, and we will consider the next handler. Otherwise,
	-- it returns a template name and arguments to call it with, but even then, that template
	-- might return an error, and we need to consider the next handler. This happens,
	-- for example, with the category "CAT:Mato Grosso, Brazil", where "Mato" is the name of
	-- a language, so the handler for {{poscatboiler}} fires and tries to find a label
	-- "Grosso, Brazil". This throws an error, and previously, this blocked fruther handler
	-- consideration, but now we check for the error and continue checking handlers;
	-- eventually, {{topic cat}} will fire and correctly handle the category.
	for _, titleObject in ipairs(titleObjects) do
		for _, handler in ipairs(handlers) do
			local templateObject, args_handled = handler(titleObject, args)
			
			if templateObject then
				require("Module:debug").track("auto cat/" .. templateObject.title)
				local cattext = frame:expandTemplate(templateObject)
				-- FIXME! We check for specific text found in most or all error messages generated
				-- by category tree templates (in particular, the second piece of text below should be
				-- in all error messages generated when a given module doesn't recognize a category name).
				-- If this text ever changes in the source modules (e.g. [[Module:category tree]],
				-- it needs to be changed here as well.)
				if cattext:find("Category:標籤無效的分類") or
					cattext:find("本分類自動產生的內容有錯誤") then
					if not first_error_cattext then
						first_error_templateObject = templateObject
						first_error_args_handled = args_handled
						first_error_cattext = cattext
					end
				else
					if not args_handled and next(args) then
						extra_args_error(templateObject)
					end
					return cattext
				end
			end
		end
	end
	
	if first_error_cattext then
		if not first_error_args_handled and next(args) then
			extra_args_error(first_error_templateObject)
		end
		return first_error_cattext
	end
	error("{{auto cat}}無法識別此分類名稱的格式")
end

-- test function for injecting title string
function export.test(title)
	if type(title) == "table" then
		title = title:getParent().args[1]
	end
	
	local titleObject = {}
	titleObject.text = title
	
	for _, handler in ipairs(handlers) do
		local t = handler(titleObject)
		
		if t then
			return t.title
		end
	end	
end

-- for [[Module:Category tree]]
function export.st(title) return st(title) end

return export