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

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

Реализация шаблонов {{Сортировка: по типам}}, {{Сортировка: по странам}} и {{Сортировка: по изображениям}}. Подмодуль конфигурации: Модуль:Autosorting/config.

Тесты: Шаблон:Сортировка: по типам/тесты и Шаблон:Сортировка: по странам/тесты.

require( 'strict' )
local p = {}

local mwLang = mw.getContentLanguage()
local getArgs = require( 'Module:Arguments' ).getArgs

local config = mw.loadData( 'Module:Autosorting/config' )

local function isEmpty( val )
	return val == nil or val == ''
end

-- Получить назвние категории
local function getCategoryName( str, name, val )
	val = val or ''
	return string.format( str, mwLang:ucfirst( name ), val )
end

-- Получить стандартный лимит
local function getPageLimit( name, property )
	name = mwLang:lcfirst( name )
	property = mwLang:uc( property )
	
	local propertyLimits = config.limits[ property ]
	if isEmpty( propertyLimits ) then
		return -1
	end
	
	return propertyLimits[ name ] or propertyLimits.default
end

-- Получить соответствие категории критериям
local function isValidCategory( name, catName, property, doNotCheck )
	if not doNotCheck then
		local success, title = pcall( mw.title.new, 'Category:' .. catName )
		if success and not isEmpty( title ) and title.exists then
			return true
		end
	end
	
	local success, catCount = pcall( mw.site.stats.pagesInCategory, catName, 'pages' )
	if success then
		local pageLimit = getPageLimit( name, property )
	
		return catCount > pageLimit
	end
	
	return false
end

-- Получить num (по умолчанию все) первых значений из свойства в Викиданных
-- TODO: переделать на получение Q-элементов, чтобы не зависеть от языка
local function getWikidataProperty( frame, property, entityId, num )
	frame = frame or mw.getCurrentFrame()
	
	local callArgs = {
		property,
	}
	if not isEmpty( entityId ) then
		callArgs.from = entityId
	end
	
	local success, result = pcall( frame.callParserFunction, frame, '#property', callArgs )
	if success and not isEmpty( result ) then
		if mw.ustring.len( result ) > 255 then
			return {}
		end
		result = mw.text.split( result, ', ' )
		
		if isEmpty( num ) then
			return result
		else
			return { unpack( result, 1, num ) }
		end
	end
	
	return {}
end

-- Получить категорию для отсутствия изображений
local function getFileCategory( frame, name, property, entityId )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local propValues = getWikidataProperty( frame, property, entityId, 3 )
	
	local catName = getCategoryName( name, property )
	local instanceOf = getWikidataProperty( frame, 'p31', entityId, 3 )
	instanceOf = #instanceOf > 0 and instanceOf[ 1 ] or ''
	local pageTitle = mw.title.getCurrentTitle().fullText
	
	local result = string.format( '[[Category:%s|%s%s]]', catName, instanceOf, pageTitle )
	return #propValues, result
end

-- Сортировка по профессиям
function p._byOccupation( frame, name, entityId )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p106'
	local propValues = getWikidataProperty( frame, property, entityId )
	
	local result = ''
	local catName = ''
	local occupationExists = false
	local validCatsCounter = 0
	for key, val in pairs( propValues ) do
		local value = mwLang:lcfirst( val )
		catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: %s)', name, value )
		occupationExists = true
		
		if isValidCategory( name, catName, property ) then
			result = result .. string.format( '[[Category:%s]]', catName )
			validCatsCounter = validCatsCounter + 1
		end
		
		-- Ограничение количества выводимых категорий
		if validCatsCounter >= 3 then
			break
		end
		
	end
	
	-- Подходящей категории нет, либо в ВД нет рода занятий, но есть дефолтные значения,
	-- тогда поставляем категорию на их основе при её наличии
	local args = getArgs( frame )
	local defaultOccupation = args[ 'default-occupation' ]
	
	if result == '' and not isEmpty( defaultOccupation ) then
		occupationExists = true
		catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: %s)', name, defaultOccupation )
		if isValidCategory( name, catName, property ) then
			result = result .. string.format( '[[Category:%s]]', catName )
		end
	end
	
	-- Есть род занятий (на ВД или через параметр), но подходящей категории нет,
	-- тогда подставляем служебную категорию "не распределён" при её наличии
	if result == '' and occupationExists then
		catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: не распределён)', name )
		if isValidCategory( name, catName, property ) then
			result = result .. string.format( '[[Category:%s]]', catName )
		end
	end
	
	return result
end

-- Сортировка по типам
function p._byType( frame, name, entityId )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p31'
	local propValues = getWikidataProperty( frame, property, entityId )

	local args = getArgs( frame )
	local defaultType = args[ 'default-type' ]
	local defaultOccupation = args[ 'default-occupation' ]
	
	local defaultCatKey
	local result = ''
	local catName = ''
	local validCatsCounter = 0
	for key, val in pairs( propValues ) do
		catName = getCategoryName( 'Википедия:%s (тип: %s)', name, mwLang:lcfirst( val ) )
		-- TODO: переделать на получение Q-элементов, чтобы не зависеть от языка
		if val == 'человек' or val == 'human' then
			local occupations = p._byOccupation( frame, name, entityId )
			if isEmpty( occupations ) then
				result = result .. string.format( '[[Category:%s]]', catName )
			else
				result = result .. occupations
			end
			validCatsCounter = validCatsCounter + 1 -- "человек" считается всегда валидной
		else
			if isValidCategory( name, catName, property ) then
				result = result .. string.format( '[[Category:%s]]', catName )
				validCatsCounter = validCatsCounter + 1
			else
				if isEmpty( defaultCatKey ) then
					defaultCatKey = val
				end
			end
		end
		
		-- Ограничение количества выводимых категорий
		if validCatsCounter >= 3 then
			break
		end
	end
	
	-- Если ничего не нашлось, попытка добавить категорию на основе переданных дефолтных значений
	if result == '' and not isEmpty( defaultType ) then
		if defaultType == 'человек' and not isEmpty( defaultOccupation ) then -- человек, есть занятие
			catName = getCategoryName( 'Википедия:%s (тип: человек; род занятий: %s)', name, defaultOccupation )
			if isValidCategory( name, catName, property ) then
				result = result .. string.format( '[[Category:%s]]', catName )
				
			else -- человек, есть занятие, но категория для занятия не прошла проверки
				catName = getCategoryName( 'Википедия:%s (тип: человек)', name )
				result = result .. string.format( '[[Category:%s]]', catName )
			end
			
		elseif defaultType == 'человек' then -- человек, нет занятия
			catName = getCategoryName( 'Википедия:%s (тип: человек)', name )
			result = result .. string.format( '[[Category:%s]]', catName )
				
		else -- нечеловек
			catName = getCategoryName( 'Википедия:%s (тип: %s)', name, defaultType )
			if isValidCategory( name, catName, property ) then
				result = result .. string.format( '[[Category:%s]]', catName )
			end
		end
	end
	
	-- Добавить стандартную категорию только при отсутствии иных
	local defaultCatName = getCategoryName( 'Википедия:%s (не распределённые по типам)', name )
	if result == '' and not isEmpty( defaultCatKey ) then
		local pageTitle = mw.title.getCurrentTitle().fullText
		result = result .. string.format( '[[Category:%s|%s%s]]', defaultCatName, defaultCatKey, pageTitle )
	end
	
	if result == '' then
		return getCategoryName( '[[Category:Википедия:%s (тип: не указан)]]', name )
	end
	
	return result
end

-- Сортировка по АТЕ
function p._bySubdivision( frame, name, entityId )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p131'
	local propValues = getWikidataProperty( frame, property, entityId, 3 )
	
	local result = ''
	for key, val in pairs( propValues ) do
		local catName = getCategoryName( 'Википедия:%s (АТЕ: %s)', name, val )
		
		if isValidCategory( name, catName, property, false ) then
			result = result .. string.format( '[[Category:%s]]', catName )
		end
	end
	
	return result
end

-- Сортировка по странам
function p._byCountry( frame, name, entityId )
	if isEmpty( name ) then
		return ''
	end
	frame = frame or mw.getCurrentFrame()
	
	local property = 'p17'
	local propValues = getWikidataProperty( frame, property, entityId, 3 )
	
	local result = ''
	for key, val in pairs( propValues ) do
		if isEmpty( val ) then
			break
		end
		local catName = getCategoryName( 'Википедия:%s (страна: %s)', name, val )
		
		result = result .. string.format( '[[Category:%s]]', catName )
		if isValidCategory( name, catName, property, false ) then
			result = result .. p._bySubdivision( frame, name, entityId )
		end
	end
	
	return result
end

-- Сортировка по наличию/отсутствию изображений
function p._byImage( frame, file, localFileProps, entityId )
	frame = frame or mw.getCurrentFrame()
	
	-- Возможный сброс значения с Викиданных
	if file ~= nil and mw.text.trim( file ) == '-' then
		file = nil
	end
	
	-- Вывести категории при заполненном несуществующем файле (= файле с Викисклада)
	if not isEmpty( file ) then
		local success, title = pcall( mw.title.new, 'File:' .. file )
		
		-- Игнорировать при заполненном локальном файле
		if success and not isEmpty( title ) and title.exists then
			return nil
		end
		
		local catName = 'Википедия:Статьи с изображениями: заполнить свойство %s в Викиданных'
		local result = ''
		local p18, p18Category = getFileCategory( frame, catName, 'p18', entityId )
		local p373, p373Category = getFileCategory( frame, catName, 'p373', entityId )
		if p18 == 0 then
			result = result .. p18Category
		end
		if p373 == 0 then
			result = result .. p373Category
		end
		
		return result
	end
	
	-- Игнорировать при наличии изображений в указанных свойствах
	for _, val in pairs( localFileProps ) do
		local propValue = getWikidataProperty( frame, val, entityId, 3 )
		if #propValue > 0 then
			return ''
		end
	end
	
	-- Вывести категории при отсутствии игнорируемых свойств
	local catName = 'Википедия:Статьи без изображений (указано в Викиданных: %s)'
	local result = p._byCountry( frame, 'статьи без изображений', entityId )
	
	local p18, p18Category = getFileCategory( frame, catName, 'p18', entityId )
	local p242, p242Category = getFileCategory( frame, catName, 'p242', entityId )
	local p373, p373Category = getFileCategory( frame, catName, 'p373', entityId )
	if p18 > 0 then
		result = result .. p18Category
	end
	if p242 > 0 then
		result = result .. p242Category
	end
	if p373 > 0 then
		result = result .. p373Category
	end
	result = result .. p._byType( frame, 'статьи без изображений', entityId )
	
	return result
end

-- Шаблон сортировки по типам
function p.byType( frame )
	local args = getArgs( frame )
	local name = args[ 1 ]
	local entityId = args[ 'from' ]
	if isEmpty( name ) or not isEmpty( args.nocat ) then
		return nil
	end
	
	if mw.ustring.find( name, 'статьи' ) and mw.title.getCurrentTitle().namespace ~= 0 then
		return nil
	end
	
	return p._byType( frame, name, entityId )
end

-- Шаблон сортировки по странам
function p.byCountry( frame )
	local args = getArgs( frame )
	local name = args[ 1 ]
	local entityId = args[ 'from' ]
	if isEmpty( name ) or not isEmpty( args.nocat ) then
		return nil
	end
	
	if mw.ustring.find( name, 'статьи' ) and mw.title.getCurrentTitle().namespace ~= 0 then
		return nil
	end
	
	return p._byCountry( frame, name, entityId )
end

-- Шаблон сортировки по изображениям
function p.byImage( frame )
	if mw.title.getCurrentTitle().namespace ~= 0 then
		return nil
	end
	local args = getArgs( frame )
	local file = args[ 1 ]
	local entityId = args[ 'from' ]
	if not isEmpty( args.nocat ) then
		return nil
	end
	
	-- Игнорирование по умолчанию статей с указанным p18
	local uses = args[ 'uses' ]
	if isEmpty( uses ) then
		uses = 'p18'
	end
	local localFileProps = mw.text.split( uses, ', ' )
	if uses == '-' then
		localFileProps = {}
	end
	
	return p._byImage( frame, file, localFileProps, entityId )
end

return p