Модуль:MetaCatDoc (Bk;rl,&MetaCatDoc)

Перейти к навигации Перейти к поиску
Документация

Модуль для создания страницы документации шаблонов через Шаблон:MetaCatDoc, использующих модули

Модуль генерирует список категорий и список шаблонов. Используется в шаблонах {{year-doc}}, {{decade-doc}}, {{century-doc}} и {{country-doc}}.

Пример: {{#invoke:MetaCatDoc|main}} в шаблоне «{{Архитектура по векам}}» покажет

Добавляет категории:

  • Искусство <век> века
  • Архитектура по векам

Добавляет шаблон:

local p = {}

local currentTitle = mw.title.getCurrentTitle()
local getArgs = require('Module:Arguments').getArgs
local romanConverter = require('Module:Roman').convert
local format = string.format

local CATEGORIES = {
	default = {
		YearMetaCat2 = 'Категория:Шаблоны для категорий по годам',
		DecadeMetaCat = 'Категория:Шаблоны для категорий по десятилетиям',
		CenturyMetaCat = 'Категория:Шаблоны для категорий по векам'
	},
	withCountry = {
		YearMetaCat2 = 'Категория:Шаблоны для категорий по странам и годам',
		DecadeMetaCat = 'Категория:Шаблоны для категорий по странам и десятилетиям',
		CenturyMetaCat = 'Категория:Шаблоны для категорий по странам и векам',
		CountryMetaCat = 'Категория:Шаблоны для категорий по странам'
	}
}

-- Переменные при использовании стран
local COUNTRY_PLACEHOLDERS = {
	'<страна>', '<страны>', '<в стране>',
	'<часть света>', '<части света>', '<в части света>',
	'<государство>', '<государства>', '<в государстве>',
	'<государство:[^>]+>', '<государства:[^>]+>', '<в государстве:[^>]+>'
}

-- Безопасно получает содержимое шаблона по его имени
local function safeGetTemplateContent(templateName)
	if not templateName then error('Имя шаблона не может быть пустым') end
	local title = mw.title.new(templateName, 10)
	if not title then error(format('Невозможно создать объект заголовка для "%s"', templateName)) end
	local content = title:getContent()
	if not content then error(format('Не удалось получить содержимое шаблона "%s"', templateName)) end
	return content
end

-- Очищает текст от служебных тегов и преобразует специальные конструкции
local function cleanText(text)
	if not text then return '' end
	return text:gsub('<noinclude>.-</noinclude>', ''):gsub('<!--.--->', ''):gsub('{{%s*([^|{}]-)%s*из заголовка[^}]*}}', '<%1>')
end

-- Определяет тип метакатегории на основе текста шаблона
local function detectMetaCategoryType(text)
	if not text then error('Текст для определения типа метакатегории не может быть пустым') end
	for _, type in ipairs({'YearMetaCat2', 'DecadeMetaCat', 'CenturyMetaCat', 'CountryMetaCat'}) do
		if text:find(type) then return type end
	end
	error('Не найден модуль (YearMetaCat2/DecadeMetaCat/CenturyMetaCat/CountryMetaCat)')
end

-- Преобразует числовое значение века в римские цифры с указанием эры
local function safeCenturyConversion(century)
	if not century then return nil end
	local num = tonumber(century)
	if not num then error(format('Неверный формат века: "%s"', century)) end
	return format('%s века%s', romanConverter(math.abs(num)), num < 0 and ' до н. э.' or '')
end

-- Форматирует категорию с указанием временного диапазона веков
local function formatCentury(category, startCentury, endCentury)
	if not category then error('Категория не может быть пустой') end
	if not (startCentury or endCentury) then return category end

	local parts = {}
	if startCentury then table.insert(parts, 'с ' .. safeCenturyConversion(startCentury)) end
	if endCentury then table.insert(parts, (startCentury and 'по' or 'до') .. ' ' .. safeCenturyConversion(endCentury)) end

	return format('%s [%s]', category, table.concat(parts, ' '))
end

-- Извлекает список категорий из кода шаблона
local function extractCategories(text, metaType)
	if not text or not metaType then return {} end
	local categories = text:match('.-{{#invoke:'..metaType..'|main(.-)}}.*') or ''
	local result = {}
	for _, category in ipairs(mw.text.split(categories, '|', true)) do
		if category ~= '' and not category:find('=') then
			table.insert(result, category)
		end
	end
	return result
end

-- Обрабатывает категории и формирует их описание
local function processCategories(text, metaType)
	if not text or not metaType then error('Отсутствуют необходимые параметры для обработки категорий') end

	local categories = extractCategories(text, metaType)
	local result = {"'''Добавляет категории:'''"}

	if #categories == 0 then
		table.insert(result, '* Нет категорий')
		return table.concat(result, '\n')
	end

	for _, category in ipairs(categories) do
		local parts = mw.text.split(category, '!', true)
		local mainCategory = mw.text.trim(parts[1])
	
		if metaType == 'CenturyMetaCat' then
			mainCategory = formatCentury(mainCategory, parts[3] and mw.text.trim(parts[3]), parts[4] and mw.text.trim(parts[4]))
		end
	
		table.insert(result, '* ' .. mainCategory)
	end

	return table.concat(result, '\n')
end

-- Извлекает параметры из вызовов модуля
local function extractParameters(text, metaType)
	if not text or not metaType then return {} end
	local params = {}

	-- Ищем вызовы модулей #invoke с аргументами range, max, min
	for invokeCall in text:gmatch('{{#invoke:'..metaType..'|main.-}}') do
		local range = invokeCall:match('range%s*=%s*(%d+)')
		local max = invokeCall:match('max%s*=%s*(%d+)')
		local min = invokeCall:match('min%s*=%s*(%d+)')

		if range or max or min then
			table.insert(params, {
				range = range,
				max = max,
				min = min
			})
		end
	end

	return params
end

-- Форматирует параметры для вывода
local function processParameters(text, metaType)
	local params = extractParameters(text, metaType)
	if #params == 0 then return nil end -- Возвращаем nil, если параметры отсутствуют

	local result = {"'''Параметры:'''"}
	for _, param in ipairs(params) do
		-- Добавляем только существующие параметры
		if param.range then
			table.insert(result, format('* range: %s', param.range))
		end
		if param.min then
			table.insert(result, format('* min: %s', param.min))
		end
		if param.max then
			table.insert(result, format('* max: %s', param.max))
		end
	end

	return table.concat(result, '\n')
end

-- Извлекает шаблоны из текста
local function extractTemplates(text, metaType)
	if not text or not metaType then return {} end
	local templates = {}

	for template in text:gmatch('{{[^#].-}}') do
		template = template:sub(3, -3):gsub('{{#invoke:'..metaType..'|expand|', '')
		local name, args = template:match('^([^|]+)|?(.*)')
	
		if name then
			args = args ~= '' and ('|' .. args:gsub('{{#', '<nowiki>{{#</nowiki>'):gsub('}}', '<nowiki>}}</nowiki>')) or ''
			table.insert(templates, {name = name, args = args})
		else
			table.insert(templates, {name = template, args = ''})
		end
	end

	return templates
end

-- Обрабатывает шаблоны и формирует их описание
local function processTemplates(frame, text, metaType)
	if not text or not metaType then error('Отсутствуют необходимые параметры для обработки шаблонов') end

	local templates = extractTemplates(text, metaType)
	if #templates == 0 then return '' end

	local result = {format("'''Добавляет шаблон%s:'''", #templates > 1 and 'ы' or '')}

	for _, template in ipairs(templates) do
		local templateText = format('<nowiki>{{</nowiki>[[Ш:%s|%s]]%s<nowiki>}}</nowiki>', 
			template.name, template.name, template.args)
		table.insert(result, '* ' .. templateText)
	end

	return table.concat(result, '\n')
end

-- Определяет категорию для шаблона
local function categorizeTemplate(text, metaType, args)
	if not text or not metaType then error('Отсутствуют необходимые параметры для категоризации шаблона') end

	if args and args.category then
		return (args.category:match('^Категория:') and args.category or 'Категория:' .. args.category)
	end

	for _, placeholder in ipairs(COUNTRY_PLACEHOLDERS) do
		if text:find(placeholder) then
			return CATEGORIES.withCountry[metaType]
		end
	end

	return CATEGORIES.default[metaType]
end

-- Возвращает категорию для шаблона
function p.getCategory(frame)
	local args = getArgs(frame)
	local templateName = currentTitle.text:gsub('/doc$', '')
	local templateText = safeGetTemplateContent(templateName)
	local metaType = detectMetaCategoryType(templateText)
	local category = categorizeTemplate(templateText, metaType, args)
	return category and ('[[' .. category .. ']]') or ''
end

-- Основная функция модуля, формирует документацию шаблона
function p.main(frame)
	local args = getArgs(frame)
	local templateName = (args['title'] or currentTitle.text):gsub('/doc$', '')
	local templateText = cleanText(safeGetTemplateContent(templateName))
	local metaType = detectMetaCategoryType(templateText)
	local category = categorizeTemplate(templateText, metaType, args)

	if category then
		templateText = templateText .. '\n[[' .. category .. ']]'
	end

	local result = processCategories(templateText, metaType)
	local templatesSection = processTemplates(frame, templateText, metaType)
	local parametersSection = processParameters(templateText, metaType)

	local output = {result}
	if templatesSection ~= '' then
		table.insert(output, templatesSection)
	end
	if parametersSection then
		table.insert(output, parametersSection)
	end

	return frame:preprocess(table.concat(output, '\n\n'))
end

-- Определяет используемые Lua-модули в шаблоне
function p.detectLua(frame)
	local templateName = (getArgs(frame)['title'] or currentTitle.text):gsub('/doc$', '')
	local moduleSet = {}

	for moduleName in safeGetTemplateContent(templateName):gmatch('{{#invoke:([^|}]+)') do
		moduleSet[moduleName] = true
	end

	local modules = {}
	for moduleName in pairs(moduleSet) do
		table.insert(modules, moduleName)
	end

	if #modules == 0 then return '' end

	local templateArgs = {
		title = 'Lua'
	}

	for i, moduleName in ipairs(modules) do
		if i == 1 then
			templateArgs[1] = moduleName
		else
			templateArgs['module' .. i] = moduleName
		end
	end
	templateArgs.nocat = '1'

	return frame:expandTemplate{ title = 'Шаблон:Lua', args = templateArgs }
end

return p