Документация

Используется в {{Статья проекта}}, {{Статистика проекта}}, {{Проектная категория}}. В прекрасной Википедии будущего будет реализовывать все / наиболее важные функции всех трёх шаблонов.

page_categories

Формирует цепочку категорий для шаблона проекта в формате, требуемом шаблоном {{Статья проекта}}.

Параметры page_categories
1 или name Название проекта
class Уровень статьи, заданный в шаблоне
importance Уровень важности, заданный в шаблоне

project_statistics

Формирует таблицу со статистикой статей проекта (см. {{статистика проекта}}).

Параметры
1 или name Название проекта.
featured Если непустой и не ноль, добавляет в таблицу статусные статьи.
list Если непустой и не ноль, добавляет в таблицу списки.
align Выравнивание таблицы — по левому краю (left), по центру (center) или по правому краю (right). По умолчанию — по правому краю.
nocat Если параметр не задан или пуст, также включает страницу в проектные категории, чтобы они не были пустыми.
minus Вычитает из числа страниц в категории указанное в данном параметре число. Используется для компенсации непустого nocat.

category_description

Формирует описания проектных категорий («Статьи проекта X», «Статьи проекта X по уровню/важности», «Статьи проекта X уровня N», «Статьи проекта X энной важности», «Статьи проекта X уровня N энной важности»), см. {{проектная категория}}. Проставляет метку __HIDDENCAT__ и верхние категории. Не принимает параметры: всю необходимую информацию берёт из заголовка категории.

По умолчанию также добавляет на страницу категории таблицу со статистикой проекта с параметрами: |featured=1|list=1|minus=1|nocat=1. Отключить таблицу можно задав непустой параметр notable. Остальные параметры будут переданы в функцию, формирующую таблицу статистики — см. #project_statistics.

require( 'strict' )
local p = {}

local yesno = require( 'Module:Yesno' )
local getArgs = require( 'Module:Arguments' ).getArgs

local module_styles_page = 'Module:WikiProject/styles.css'

local templatestyles_page = 'Шаблон:Статья проекта/styles.css'
local templatestyles_class = 'ts-Статья_проекта-rating '

local hiddencat = '__HIDDENCAT__'

local class_info = {
	['ис'] = {
		category = 'Избранные статьи проекта %s',
		class = 'level-fa',
		image = '[[Файл:Small Skew Star SVG.svg|baseline|link=|Избранная статья]]',
		shortname = 'Избр.',
		sortKey = ' 0'
	},
	['исп'] = {
		category = 'Избранные списки проекта %s',
		class = 'level-fl',
		image = '[[Файл:Feat_lists.svg|14px|baseline|link=|Избранный список]]',
		shortname = 'ИСП',
		sortKey = ' 1'
	},
	['хс'] = {
		category = 'Хорошие статьи проекта %s',
		class = 'level-ga',
		image = '[[Файл:Blue star unboxed.svg|13px|baseline|link=|Хорошая статья]]',
		shortname = 'Хор.',
		sortKey = ' 2'
	},
	['дс'] = {
		category = 'Добротные статьи проекта %s',
		class = 'level-aa',
		image = '[[Файл:Crystal Clear action bookmark Silver approved.svg|baseline|14px|link=|Добротная статья]]',
		shortname = 'Добр.',
		sortKey = ' 3'
	},
	['i'] = {
		category = 'Статьи проекта %s I уровня',
		class = 'level-1',
		shortname = 'I',
		sortKey = ' 4'
	},
	['ii'] = {
		category = 'Статьи проекта %s II уровня',
		class = 'level-2',
		shortname = 'II',
		sortKey = ' 5'
	},
	['iii'] = {
		category = 'Статьи проекта %s III уровня',
		class = 'level-3',
		shortname = 'III',
		sortKey = ' 6'
	},
	['iv'] = {
		category = 'Статьи проекта %s IV уровня',
		class = 'level-4',
		shortname = 'IV',
		sortKey = ' 7'
	},
	['список'] = {
		category = 'Списки проекта %s',
		class = 'level-list',
		shortname = 'Список',
		sortKey = ' 8'
	},
	['неизвестный'] = {
		category = 'Статьи проекта %s неизвестного уровня',
		shortname = 'Неизв.',
		sortKey = ' 9'
	},
}

local importance_info = {
	['высшая'] = {
		category = ' высшей важности',
		class = 'importance-highest',
		shortname = 'Высш.',
		sortKey = ' 0'
	},
	['высокая'] = {
		category = ' высокой важности',
		class = 'importance-high',
		shortname = 'Выс.',
		sortKey = ' 1'
	},
	['средняя'] = {
		category = ' средней важности',
		class = 'importance-medium',
		shortname = 'Сред.',
		sortKey = ' 2'
	},
	['низкая'] = {
		category = ' низкой важности',
		class = 'importance-low',
		shortname = 'Низ.',
		sortKey = ' 3'
	},
	['неизвестная'] = {
		category = ' неизвестной важности',
		shortname = 'Неизв.',
		sortKey = ' 9'
	},
}

-- Вспомогательные строковые функции
local function isEmpty( val )
	return val == nil or val == ''
end

local function startsWith( str, prefix )
	return mw.ustring.sub( str, 1, mw.ustring.len( prefix ) ) == prefix
end

local function cutPrefix( str, length )
	return mw.ustring.sub( str, length + 1 )
end

local function endsWith( str, suffix )
	return mw.ustring.sub( str, -mw.ustring.len( suffix ) ) == suffix
end

local function cutSuffix( str, length )
	return mw.ustring.sub( str, 1, -length - 1 )
end

-- Функции для доступа к данным
local function getInfo( data, key )
	if isEmpty( key ) then
		return nil
	end
	
	key = key and mw.ustring.lower( key )
	return data[ key ]
end

local function getClassInfo( class )
	return getInfo( class_info, class )
end

local function getImportanceInfo( importance )
	return getInfo( importance_info, importance )
end

--[[
Аналог шаблона «Статистика проекта/Категория», только с общепринятыми названиями уровней и
важностей. Для неизвестного уровня используется параметр 'неизвестный', для неизвестной важности —
'неизвестная'. Если оставить эти параметры пустыми, уровень/важность указана не будет.

— form_cat_name( 'Музыка', 'ИС', 'высшая' )
→ 'Избранные статьи проекта Музыка высшей важности'
— form_cat_name( 'Музыка', 'I', 'неизвестная' )
→ 'Статьи проекта Музыка I уровня неизвестной важности'
— form_cat_name( 'Музыка', '', 'средняя')
→ 'Статьи проекта Музыка средней важности'
]]
local function form_cat_name( project, class, importance )
	local result = ''

	local classInfo = getClassInfo( class )
	if classInfo ~= nil then
		result = string.format( classInfo.category, project )
	else
		result = 'Статьи проекта ' .. project
	end

	local importanceInfo = getImportanceInfo( importance )
	if importanceInfo ~= nil then
		result = result .. importanceInfo.category
	end

	return result
end

--[[
Обратная предыдущей функция: парсит название категории и возвращает таблицу вида
{ project, class, importance, is_error }, где is_error принимает значение true, если
название категории имеет некорректный вид.
]]
local function parse_cat_name( category )
	-- Эх, если бы тут были полноценные регулярные выражения... =\
	local result = {
		project = '',
		class = '',
		importance = '',
		is_error = false
	}

	if endsWith( category, ' по уровню' ) then
		if not startsWith( category, 'Статьи проекта ' ) then
			result.is_error = true
			return result
		end
		category = cutPrefix( category, 15 )
		category = cutSuffix( category, 10 )
		result.project = category
		result.class = '*'
		return result
	end

	if endsWith( category, ' по важности' ) then
		if not startsWith( category, 'Статьи проекта ' ) then
			result.is_error = true
			return result
		end
		category = cutPrefix( category, 15 )
		category = cutSuffix( category, 12 )
		result.project = category
		result.importance = '*'
		return result
	end

	if endsWith( category, ' высшей важности' ) then
		category = cutSuffix( category, 16 )
		result.importance = 'высшая'
	elseif endsWith( category, ' высокой важности' ) then
		category = cutSuffix( category, 17)
		result.importance = 'высокая'
	elseif endsWith( category, ' средней важности' ) then
		category = cutSuffix( category, 17 )
		result.importance = 'средняя'
	elseif endsWith( category, ' низкой важности' ) then
		category = cutSuffix( category, 16 )
		result.importance = 'низкая'
	elseif endsWith( category, ' неизвестной важности' ) then
		category = cutSuffix( category, 21 )
		result.importance = 'неизвестная'
	end

	if startsWith( category, 'Избранные статьи проекта ' ) then
		category = cutPrefix( category, 25 )
		result.class = 'ИС'
	elseif startsWith( category, 'Избранные списки проекта ' ) then
		category = cutPrefix( category, 25 )
		result.class = 'ИСП'
	elseif startsWith( category, 'Хорошие статьи проекта ' ) then
		category = cutPrefix( category, 23 )
		result.class = 'ХС'
	elseif startsWith( category, 'Добротные статьи проекта ' ) then
		category = cutPrefix( category, 25 )
		result.class = 'ДС'
	elseif startsWith( category, 'Списки проекта ' ) then
		category = cutPrefix( category, 15 )
		result.class = 'Список'
	elseif startsWith( category, 'Статьи проекта ' ) then
		category = cutPrefix( category, 15 )
	else
		result.is_error = true
	end

	if endsWith( category, ' I уровня' ) then
		if result.class ~= '' then
			result.is_error = true
		end
		category = cutSuffix( category, 9 )
		result.class = 'I'
	elseif endsWith( category, ' II уровня' ) then
		if result.class ~= '' then
			result.is_error = true
		end
		category = cutSuffix( category, 10 )
		result.class = 'II'
	elseif endsWith( category, ' III уровня' ) then
		if result.class ~= '' then
			result.is_error = true
		end
		category = cutSuffix( category, 11 )
		result.class = 'III'
	elseif endsWith( category, ' IV уровня' ) then
		if result.class ~= '' then
			result.is_error = true
		end
		category = cutSuffix( category, 10 )
		result.class = 'IV'
	elseif endsWith( category, ' неизвестного уровня' ) then
		if result.class ~= '' then
			result.is_error = true
		end
		category = cutSuffix( category, 20 )
		result.class = 'неизвестный'
	end

	result.project = category
	return result
end

--[[
Выводит код категории на основе имени и сортировочного ключа / текста ссылки (в случае ссылок)
]]
local function get_cat_link( name, after, isLink )
	if isEmpty( name ) then
		return ''
	end
	return string.format(
		'[[%sКатегория:%s%s]]',
		isLink == true and ':' or '',
		name,
		not isEmpty( after ) and '|' .. after or ''
	)
end

--[[
Добавление заголовка таблицы с правильными классами в get_project_statistics
]]
local function add_table_header( html, info, catName, scope )
	local headerText = get_cat_link( catName, info.shortname, true )
	if info.image then
		headerText = info.image .. ' ' .. headerText
	end
	return html:tag( 'th' )
		:attr( 'scope', scope or 'row' )
		:addClass( templatestyles_class .. (info.class or '') )
		:wikitext( headerText )
end


--[[
Замена шаблону «Статья проекта/Категория» в рамках {{Статья проекта}}, кроме части с «Статьи несуществующего проекта»
]]
function p._get_page_categories( name, class, importance )
	if isEmpty( name ) then
		return
	end
	
	-- Поддержка арабских цифр
	local romanNumerals = {
		'i',
		'ii',
		'iii',
		'iv',
	}
	if tonumber( class ) ~= nil then
		class = romanNumerals[ tonumber( class ) ]
	end
	
	class = not isEmpty( class ) and mw.ustring.lower( class )
	importance = not isEmpty( importance ) and mw.ustring.lower( importance )
	
	local classOrUnknown = getClassInfo( class ) ~= nil and class or 'неизвестный'
	local importanceOrUnknown = getImportanceInfo( importance ) ~= nil and importance or 'неизвестная'
	
	-- Статьи проекта X
	return get_cat_link( form_cat_name( name, nil, nil ) )
		-- Статьи проекта X Y важности
		.. get_cat_link( form_cat_name( name, nil, importanceOrUnknown ) )
		-- Статьи проекта X Y уровня
		.. get_cat_link( form_cat_name( name, classOrUnknown, nil ) )
		-- Статьи проекта X Y уровня A важности
		.. get_cat_link( form_cat_name( name, classOrUnknown, importanceOrUnknown ) )
end

function p.page_categories( frame )
	local args = getArgs( frame )
	local name = args[ '1' ] or args[ 'name' ]
	local class = args[ 'class' ]
	local importance = args[ 'importance' ]
	
	return p._get_page_categories( name, class, importance )
end

--[[
Защищает шаблоны от подстановки через подстановку их же самих с переданными параметрами
]]
function p._substing( frame )
	local args = getArgs( frame, {
		parentOnly = true,
	} )
	local mTemplateInvocation = require( 'Module:Template invocation' )
	local name = mTemplateInvocation.name( frame:getParent():getTitle() )
	
	return mTemplateInvocation.invocation( name, args )
end

--[[
Составляет табличку со статистикой статей проекта по уровню и важности. Если параметр nocat не
задан, также добавляет страницу во все проектные категории и в категорию
«Категория:Википедия:Статистика по проектам».
]]
function p._get_project_statistics( name, args )
	local frame = mw.getCurrentFrame()
	if isEmpty( name ) then
		local title = mw.title.getCurrentTitle()
		local project
		if title.namespace == 104 then
			project = title.rootText
		else
			project = 'Проект'
		end
		return 'Скопируйте стандартную строку: <code>{{Статистика проекта|' .. project ..
		       '|featured=1|list=1|nocat=&lt;includeonly&gt;1&lt;/includeonly&gt;|minus=1}}</code>'
	end
	local minus = tonumber( args['minus'] ) or 0
	local align = args['align'] or 'right'
	local featured = yesno( args['featured'] )
	local list = yesno( args['list'] )
	local nocat = yesno( args['nocat'] )

	local displayed_importances = { 'высшая', 'высокая', 'средняя', 'низкая', 'неизвестная' }
	local displayed_classes
	if featured and list then
		displayed_classes = { 'ИС', 'ИСП', 'ХС', 'ДС', 'I', 'II', 'III', 'IV', 'список', 'неизвестный' }
	elseif featured then
		displayed_classes = { 'ИС', 'ХС', 'ДС', 'I', 'II', 'III', 'IV', 'неизвестный' }
	elseif list then
		displayed_classes = { 'I', 'II', 'III', 'IV', 'список', 'неизвестный' }
	else
		displayed_classes = { 'I', 'II', 'III', 'IV', 'неизвестный' }
	end

	local all_pages = 0
	local importance_pages = {}

	local wrapper = mw.html.create( 'div' )
		:addClass( 'ts-module-WikiProject-wrapper' )
	
	local result = wrapper:tag( 'table' )
		:addClass( 'ts-module-WikiProject-table wikitable' )
	
	if align == 'left' or align == 'center' or align == 'right' then
		wrapper:addClass( 'is-' .. align )
	end
	
	result:tag( 'caption' )
		:wikitext( '[[Проект:' .. name .. '/Оценки|Статьи]] проекта [[Проект:' .. name .. '|«' .. name .. '»]]' )
		:done()
	
	result:tag( 'tr' )
		:tag( 'th' )
			:attr( 'scope', 'col' )
			:attr( 'rowspan', 2 )
			:wikitext( get_cat_link( 'Статьи проекта ' .. name .. ' по уровню', 'Уровень<br>качества', true ) )
		:tag( 'th' )
			:attr( 'scope', 'colgroup' )
			:attr( 'colspan', 6 )
			:wikitext( get_cat_link( 'Статьи проекта ' .. name .. ' по важности', 'Важность', true ) )
		:done()
	
	result:tag( 'tr' )
	for _, importance in ipairs(displayed_importances) do
		local info = getImportanceInfo( importance )
		importance_pages[ importance ] = 0
		add_table_header( result, info, form_cat_name( name, '', importance ), 'col' )
	end
	result:tag( 'th' )
		:attr( 'scope', 'col' )
		:wikitext( get_cat_link( 'Статьи проекта ' .. name, 'Всего', true ) )
		:done()

	for _, class in ipairs(displayed_classes) do
		local info = getClassInfo( class )

		local class_pages = 0

		local tr = result:tag( 'tr' )
		local class_catname = form_cat_name( name, class, '' )
		add_table_header( tr, info, class_catname )

		for _, importance in ipairs(displayed_importances) do
			local td = tr:tag( 'td' )

			local catname = form_cat_name( name, class, importance )
			local pages = mw.site.stats.pagesInCategory( catname, 'pages' ) - minus
			if pages > 0 then
				all_pages = all_pages + pages
				class_pages = class_pages + pages
				importance_pages[importance] = importance_pages[importance] + pages

				td:wikitext( get_cat_link( catname, pages, true ) )
			end
		end

		tr:tag( 'td' )
			:addClass( 'ts-module-WikiProject-table-total' )
			:wikitext( get_cat_link( class_catname, class_pages, true ) )
	end

	local totalTr = result:tag( 'tr' )
		:addClass( 'ts-module-WikiProject-table-total' )
	
	totalTr:tag( 'th' )
		:attr( 'scope', 'row' )
		:wikitext( get_cat_link( 'Статьи проекта ' .. name, 'Всего', true ) )
	for _, importance in ipairs(displayed_importances) do
		totalTr:tag( 'td' )
			:wikitext( get_cat_link( form_cat_name( name, '', importance ), importance_pages[importance], true ) )
	end
	totalTr:tag( 'td' )
		:wikitext( get_cat_link( 'Статьи проекта ' .. name, all_pages, true ) )

	local footer = 'Статистика обновляется автоматически ' ..
		'[' .. frame:expandTemplate{ title = 'очистить кэш', args = { ['1'] = 'обновить' } } .. ']'
	wrapper:tag( 'div' )
		:wikitext( footer )

	local cats = ''
	local sortKey = '*'
	if not nocat then
		cats = cats .. get_cat_link( 'Википедия:Статистика по проектам', name )
		for _, class in ipairs( displayed_classes ) do
			cats = cats .. get_cat_link( form_cat_name( name, class, '' ), sortKey )
		end
		for _, importance in ipairs(displayed_importances) do
			cats = cats .. get_cat_link( form_cat_name( name, '', importance ), sortKey )
		end
		for _, class in ipairs( displayed_classes ) do
			for _, importance in ipairs(displayed_importances) do
				cats = cats .. get_cat_link( form_cat_name( name, class, importance ), sortKey )
			end
		end
	end

	return frame:extensionTag{
		name = 'templatestyles',
		args = { src = module_styles_page }
	} .. frame:extensionTag{
		name = 'templatestyles',
		args = { src = templatestyles_page }
	} .. tostring( wrapper ) .. cats
end

function p.project_statistics( frame )
	local args = getArgs( frame )
	local name = args[ '1' ] or args[ 'name' ]
	
	if mw.isSubsting() then
		return p._substing( frame )
	end
	
	return p._get_project_statistics( name, args )
end

--[[
Заполняет страницу для категории: заполняет верхние категории, добавляет __HIDDENCAT__.
]]
function p.category_description( frame )
	local args = getArgs( frame )
	local title = mw.title.getCurrentTitle()
	if title.namespace ~= 14 then
		return ''
	end
	
	if mw.isSubsting() then
		return p._substing( frame )
	end

	local parsed_title = parse_cat_name( title.text )
	if parsed_title.is_error then
		return '<strong class="error">Ошибка модуля WikiProject: не удалось распарсить заголовок.</strong>'
	end
	
	local project = parsed_title.project
	local class = parsed_title.class
	local importance = parsed_title.importance
	local name = args.name
	local result = ''

	if class == '' and importance == '' then
		-- Статьи проекта X
		result = result .. frame:expandTemplate{ title = 'Индекс категории АБВ (удобный)', args = { ['depth'] = '2' } }
		result = result .. '\n' .. get_cat_link( 'Проект:' .. project )
	elseif class == '*' then
		-- Статьи проекта X по уровню
		result = result .. frame:expandTemplate{ title = 'метакатегория' }
		result = result .. '\n' .. get_cat_link( 'Википедия:Статьи проектов по уровню', project )
		result = result .. '\n' .. get_cat_link( 'Статьи проекта ' .. project, '*' )
	elseif importance == '*' then
		-- Статьи проекта X по важности
		result = result .. frame:expandTemplate{ title = 'метакатегория' }
		result = result .. '\n' .. get_cat_link( 'Википедия:Статьи по важности', project )
		result = result .. '\n' .. get_cat_link( 'Статьи проекта ' .. project, '*' )
	elseif importance == '' then
		-- Статьи проекта X уровня N
		result = result .. hiddencat
		if class == 'Список' then
			result = result .. '\n' .. get_cat_link( 'Списки по проектам', project )
		elseif class == 'ИСП' then
			result = result .. '\n' .. get_cat_link( 'Избранные списки по проектам', project )
		elseif class == 'ИС' then
			result = result .. '\n' .. get_cat_link( 'Избранные статьи по проектам', project )
		elseif class == 'ХС' then
			result = result .. '\n' .. get_cat_link( 'Хорошие статьи по проектам', project )
		elseif class == 'ДС' then
			result = result .. '\n' .. get_cat_link( 'Добротные статьи по проектам', project )
		elseif class == 'неизвестный' then
			result = result .. '\n' .. get_cat_link( 'Статьи неизвестного уровня', project )
		else
			result = result .. '\n' .. get_cat_link( 'Статьи ' .. class .. ' уровня', project )
		end
		result = result .. '\n' .. get_cat_link( 'Статьи проекта ' .. project .. ' по уровню', getClassInfo( class ).sortKey )
	elseif class == '' then
		-- Статьи проекта X энной важности
		result = result .. hiddencat
		result = result .. '\n' .. get_cat_link( 'Статьи' .. getImportanceInfo( importance ).category, project )
		result = result .. '\n' .. get_cat_link( 'Статьи проекта ' .. project .. ' по важности', getImportanceInfo( importance ).sortKey )
	else
		-- Статьи проекта X уровня N энной важности
		result = result .. '\n' .. get_cat_link( form_cat_name( project, class, '' ), getImportanceInfo( importance ).sortKey )
		result = result .. '\n' .. get_cat_link( form_cat_name( project, '', importance ), getClassInfo( class ).sortKey )
	end
	
	if not yesno( args['notable'] ) then
		name = project
		if args['align'] == nil then
			args['align'] = 'right'
		end
		if args['minus'] == nil then
			args['minus'] = '1'
		end
		if args['featured'] == nil then
			args['featured'] = '1'
		end
		if args['list'] == nil then
			args['list'] = '1'
		end
		args['nocat'] = '1'
		result = result .. p._get_project_statistics( name, args )
	end

	return result
end

return p