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

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

На данный момент реализует вывод шаблона {{Неоднозначность}} и часть вывода {{Категория-неоднозначность}}. Со временем будет дописан, чтобы выполнять разного рода проверки на страницах значений.

Код вызова модуля:

{{#invoke:Disambig|main}}

Данные

Вызывает и обрабатывает Module:Disambig/data.json для данных о разных типах значений. Все доступные типы (кроме служебных) можно увидеть на Шаблон:Неоднозначность#Поддерживаемые типы. Данные на странице данных представлены в следующем виде:

	"ключ": {
		"aliases": [
			"алиас 1",
            "алиас 2",
            "алиас_3"
		],
		"image": "иконка",
		"desc": "описание формата «Список статей о X»",
		"short": "необязательно: краткое описание того же формата",
		"about": "уточнение формата «о конкретном X»",
		"seeAlsoCategory": "для персоналий: категория со списком всех статей с названием",
		"category": "подкатегория формата «Страницы значений:X»"
	}

По возможности следует добавлять минимальное число алиасов (желательно 0).

Минимальный пустой шаблон для вставки нового типа на страницу:

	"ключ": {
		"image": "",
		"desc": "",
		"about": "",
		"category": ""
	}

Новые типы должны обсуждаться на Обсуждение проекта:Страницы значений перед добавлением.

Функции

  • p.main / p._main — вызов шаблона {{Неоднозначность}}.
  • p.alias — вызов шаблона {{Неоднозначность}} из шаблонов-обёрток для их корректной подстановки и учёта во включениях основного шаблона.
  • p.category / p._category — генерация списка категорий в шаблоне {{Категория-неоднозначность}}.
  • p.doc / p._doc — генерация автоматической таблицы документации известных типов значений на странице Шаблон:Неоднозначность/doc.
  • p.templateData / p._templateData — добавление известных типов значений в блок TemplateData на странице документации.
require( 'strict' )
local p = {}

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

-- [[Module:Disambig/styles.css]]
local templateStylesPage = 'Module:Disambig/styles.css'
local data = mw.loadJsonData( 'Module:Disambig/data.json' )

local defaultData = data[ '--disambig' ]
local currentTitle = mw.title.getCurrentTitle()

-- Uses ABOUT_STR/ABOUT_DEFAULT at the end
local DISAMBIG_HELP = 'Википедия:Неоднозначность'
local DISAMBIG_HELP_LABEL = 'Справка о страницах значений'
local DISAMBIG_REFLIST = 'Примечания'
local DISAMBIG_TYPES = 'На этой странице приведены:'

local FIX_LINKS = 'Если вы попали сюда из [%s <span title="Ссылки на эту страницу из других статей">другой статьи Википедии</span>], пожалуйста, вернитесь и [[Википедия:Толкование ссылок|уточните ссылку]] так, чтобы она указывала на %s.'
local SEE_ALSO_LINK = ' См. также [[Special:PrefixIndex/%s|список страниц с «%s» в начале названия]].'
local SEE_ALSO_CAT = ' См. также [%s <span title="%s">полный список существующих статей</span>].'
local ABOUT_STR = 'статью %s'
local ABOUT_DEFAULT = 'нужную статью'

local DISAMBIG_DOC = 'Текущие типы значений'
local DISAMBIG_DOC_CODE = 'Код'
local DISAMBIG_DOC_VIEW = 'Отображение'

local CATEGORY_ERROR = 'Укажите по крайней мере две категории'
local IN_CATEGORY = 'в&nbsp;категории'

-- Lua patterns for words where the module needs to insert &nbsp; (see formatText method)
local NBSP_PATTERNS = { 'во?', 'для', 'же', 'и', 'из', 'или', 'на', 'об?', 'со?', '[Сс]м%.', 'чтобы' }

local function getAliases()
	local result = {}
	for key, val in pairs( data ) do
		if val[ 'aliases' ] then
			for i, alias in ipairs( val[ 'aliases' ] ) do
				result[ alias ] = key
			end
		end
	end
	
	return result
end

local aliases = getAliases()

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

local function isNotOther( dtype )
	local aliasesOther = data[ '--other' ][ 'aliases' ]
	for i, alias in ipairs( aliasesOther ) do
		if dtype == alias then
			return false
		end
	end
	
	return true
end

local function pageNameBase( page )
	return mw.ustring.gsub( page, '^%s*(.+)%s+%b()%s*$', '%1' )
end

local function setCategory( title, key )
	if isEmpty( title ) then
		return ''
	end
	if not isEmpty( key ) then
		title = title .. '|' .. key
	end
	
	return string.format( '[[Category:%s]]', title )
end

local function formatText( str, frame )
	if isEmpty( str ) then
		return ''
	end
	
	for i, val in ipairs( NBSP_PATTERNS ) do
		-- last symbol is Unicode &nbsp; to allow replacements near &nbsp; itself
		str = mw.ustring.gsub( str, '(%s)(' .. val .. ') ', '%1%2 ' )
	end
	
	if frame ~= nil then
		return frame:preprocess( str )
	end
	return str
end

local function getFixMessage( about )
	if isEmpty( about ) then
		about = ABOUT_DEFAULT
	else
		about = string.format( ABOUT_STR, about )
	end
	
	local page = currentTitle.fullText
	local wlh = mw.title.new( 'Special:WhatLinksHere/' .. page )
	return string.format(
		formatText( FIX_LINKS ),
		wlh:fullUrl( 'namespace=0' ),
		about
	)
end

local function getSeeAlsoLink( category )
	local page = pageNameBase( currentTitle.fullText )
	
	if not isEmpty( category ) then
		local cat = mw.title.new( 'Category:' .. category )
		return string.format(
			formatText( SEE_ALSO_CAT ),
			cat:fullUrl( 'from=' .. mw.uri.encode( page ) ),
			cat.fullText
		)
	end
	
	return string.format(
		formatText( SEE_ALSO_LINK ),
		page,
		page
	)
end

-- Renders a single disambiguation type message
function p._renderPart( frame, data, isSingle, dtype )
	if frame == nil then
		frame = mw.getCurrentFrame()
	end
	
	if type( data ) ~= 'table' or isEmpty( data[ 'desc' ] ) or isEmpty( data[ 'image' ] ) then
		return p._renderPart( frame, data[ '--error' ], isSingle, dtype )
	end
	
	local result = mw.html.create( 'li' )
	if isSingle then
		result = mw.html.create( 'div' )
	end
	result:addClass( 'ts-disambig-block' )
	
	local imageSize = isSingle and '30px' or '20px'
	result:tag( 'div' )
		:addClass( 'ts-disambig-image noprint' )
		:wikitext(
			string.format( '[[File:%s|%s|class=noresize|link=|alt=]]', data.image, imageSize )
		)
	
	local text = formatText( data[ 'desc' ] )
	local addendum = ''
	local textDiv = result:tag( 'div' )
		:addClass( 'ts-disambig-text' )
	if isSingle then
		if text:find( 'class="error"' ) then
			text = formatText( string.format( text, dtype ), frame )
		else
			local fullStop = text:find( '%.$' ) == nil and '.' or ''
			text = string.format( '[[%s|%s%s]]', DISAMBIG_HELP, text, fullStop )
		end
		textDiv:tag( 'div' ):wikitext( text )
		
		addendum = getSeeAlsoLink( data[ 'seeAlsoCategory' ] )
		
		textDiv:tag( 'div' )
			:wikitext( getFixMessage( data[ 'about' ] ) .. addendum )
	else
		if data[ 'short' ] then
			text = formatText( data[ 'short' ], frame )
		end
		
		if text:find( 'class="error"' ) then
			text = formatText( string.format( text, dtype ), frame )
		else
			text = formatText( text ) .. '.'
		end
		
		if data[ 'short' ] == '' then
			text = ''
		end
		
		if not isEmpty( data[ 'seeAlsoCategory' ] ) then
			addendum = getSeeAlsoLink( data[ 'seeAlsoCategory' ] )
		end
		
		textDiv:wikitext( text .. addendum )
	end
	
	if currentTitle.namespace == 0 then
		-- Assign default category here since it won’t be otherwise
		if isSingle and currentTitle.namespace == 0 then
			textDiv:wikitext( setCategory( defaultData[ 'category' ] ) )
		end
		
		textDiv:wikitext( setCategory( data[ 'category' ], data[ 'categoryKey' ] ) )
		
		if not isEmpty( data[ 'add' ] ) then
			textDiv:wikitext( formatText( data[ 'add' ], frame ) )
		end
	end

	return result
end

-- Chooses which disambiguation type message to show
function p._getPart( frame, dtype, isSingle )
	if isEmpty( dtype ) then
		return p._renderPart( frame, data[ '--error' ], isSingle, '*' ), dtype
	end
	
	dtype = mw.ustring.lower( dtype )
	if dtype:find( '/' ) then
		dtype = mw.text.split( dtype, '/' )[ 1 ]
	end
	
	local alias = aliases[ dtype ]
	if data[ dtype ] == nil and alias == nil then
		return p._renderPart( frame, data[ '--error' ], isSingle, dtype ), dtype
	end
	
	if data[ dtype ] ~= nil then
		return p._renderPart( frame, data[ dtype ], isSingle, dtype ), dtype
	end
	
	return p._renderPart( frame, data[ alias ], isSingle, dtype ), alias
end

-- Documentation for [[Template:Disambig]]
function p._doc( frame )
	if frame == nil then
		frame = mw.getCurrentFrame()
	end
	local result = mw.html.create( 'table' )
		:addClass( 'wikitable sortable plainlinks' )
	
	result
		:tag( 'caption' )
		:wikitext( DISAMBIG_DOC )
	
	result:tag( 'tr' )
		:tag( 'th' )
			:attr( 'scope', 'col' )
			:wikitext( DISAMBIG_DOC_CODE )
		:tag( 'th' )
			:attr( 'scope', 'col' )
			:wikitext( DISAMBIG_DOC_VIEW )
	
	for dtype, val in pairs( data ) do
		local als = val[ 'aliases' ]
		if dtype:find( '^%-%-' ) == nil or dtype == '--other' then
			local displayedType = dtype
			local aliases = ''
			if dtype == '--other' then
				displayedType = als[ 1 ]
			end
			if als then
				for i, alias in ipairs( als ) do
					if dtype ~= '--other' or i ~= 1 then
						aliases = string.format( '%s, <code>%s</code>', aliases, alias )
					end
				end
				aliases = aliases:gsub( ', ', '', 1 )
				aliases = string.format( '<br><i style="opacity:0.85">%s</i>', aliases )
			end
			
			-- Replace <li> to <div> here
			local template = tostring( p._getPart( frame, dtype, false ) )
			template = mw.ustring.gsub( template, '<(/?)li', '<%1div' )
			
			result:tag( 'tr' )
				:tag( 'td' )
					:attr( 'style', 'font-size:85%' )
					:wikitext( string.format( '<code>%s</code>%s', displayedType, aliases ) )
				:tag( 'td' )
					:attr( 'style', 'font-style:italic' )
					:wikitext( template )
		end
	end
	
	return tostring( result )
end

function p.doc( frame )
	return p._doc( frame )
end

-- Generate suggested values in TemplateData blocks
function p._templateData( frame, content )
	if isEmpty( content ) then
		return ''
	end
	if frame == nil then
		frame = mw.getCurrentFrame()
	end
	
	
	local typeCount = 1
	local suggestedValues = {}
	for dtype, val in pairs( data ) do
		if dtype:find( '^%-%-' ) == nil or dtype == '--other' then
			if dtype == '--other' then
				suggestedValues[ typeCount ] = val[ 'aliases' ][ 1 ]
			else
				suggestedValues[ typeCount ] = dtype
			end
			typeCount = typeCount + 1
		end
	end
	suggestedValues = table.concat( suggestedValues, '", "' )
	
	content = mw.ustring.gsub(
		content,
		'"suggestedvalues": %[%]',
		string.format( '"suggestedvalues": ["%s"]', suggestedValues )
	)
	return frame:extensionTag{
		name = 'templatedata',
		content = content,
	}
end

function p.templateData( frame )
	local args = getArgs( frame )
	local content = args[ 1 ]
	
	return p._templateData( frame, content )
end

-- Checks for errors in page code
function p._checkErrors( frame, args )
	if currentTitle.namespace ~= 0 then
		return ''
	end
	
	local content = currentTitle:getContent()
	if isEmpty( content ) then
		return ''
	end
	content = mw.text.trim( content )

	-- Case-insensitive template name
	local template = frame:getParent():getTitle()
	local mwTitle = mw.title.new( template )
	local templatePattern = string.format(
		"[%s%s]%s",
		mw.ustring.upper( mw.ustring.sub( mwTitle.text, 1, 1 ) ),
		mw.ustring.lower( mw.ustring.sub( mwTitle.text, 1, 1 ) ),
		mw.ustring.gsub( mwTitle.text, '^.', '' )
	)
	
	-- Only works on pages with direct transclusions
	if mw.ustring.find( content, templatePattern ) == 3 then
		return setCategory( data[ '--error' ][ 'category' ], '↓' )
	end
	
	for key, val in pairs( args ) do
		if type( key ) ~= 'number' then
			return setCategory( data[ '--error' ][ 'category' ], '~' )
		end
	end
	
	return ''
end

-- Protects templates from substitution by substituting them with their own parameters
function p._substing( frame, args, template )
	if args == nil then
		args = getArgs( frame, {
			parentOnly = true,
		} )
	end
	local mTemplateInvocation = require( 'Module:Template invocation' )
	local name = mTemplateInvocation.name( template or frame:getParent():getTitle() )
	
	return mTemplateInvocation.invocation( name, args )
end

-- Renders {{Disambig}} template
function p._main( frame, args )
	if frame == nil then
		frame = mw.getCurrentFrame()
	end
	local result = mw.html.create( 'div' )
		:addClass( 'ts-disambig' )
	
	local reflist = result:tag( 'div' )
		:addClass( 'ts-disambig-reflist' )
	
	reflist:tag( 'div' )
		:attr( 'role', 'heading' )
		:attr( 'aria-level', 2 )
		:wikitext( DISAMBIG_REFLIST )
	reflist:wikitext( frame:extensionTag{
		name = 'references',
	} )
	
	local disambig = result:tag( 'div' )
		:attr( 'id', 'disambig' )
		:attr( 'role', 'note' )
		:addClass( 'ts-disambig-mbox metadata plainlinks' )
	
	if isEmpty( args[ 1 ] ) then
		local hasTypes = false
		for i, dtype in pairs( args ) do
			if tonumber( i ) ~= nil and not isEmpty( dtype ) then
				hasTypes = true
				break
			end
		end
		
		if not hasTypes then
			disambig:node( p._getPart( frame, '--disambig-single', true ) )
		else
			disambig:node( p._renderPart( frame, data[ '--error' ], true, '()' ) )
		end
	elseif isEmpty( args[ 2 ] ) then
		local dtype = args[ 1 ]
		disambig:node( p._getPart( frame, dtype, true ) )
	else
		disambig:node( p._getPart( frame, '--disambig', true ) )
		
		disambig:tag( 'div' )
			:addClass( 'ts-disambig-beforeList' )
			:wikitext( DISAMBIG_TYPES )
		
		local list = disambig:tag( 'ul' )
			:attr( 'role', 'list' )
			:addClass( 'ts-disambig-list' )
		local hasOther = false
		
		local usedTypes = {}
		for i, dtype in ipairs( args ) do
			if isNotOther( dtype ) then
				local part, usedType = p._getPart( frame, dtype, false )
				if not usedTypes[ usedType ] then
					list:node( part )
					usedTypes[ usedType ] = true
				else
					list:node( p._renderPart( frame, data[ '--error' ], false, dtype ) )
				end
			else
				hasOther = true
			end
		end
		
		if hasOther then
			list:node( p._getPart( frame, '--other', false ) )
		end
	end
	
	result = tostring( result )
	if currentTitle.namespace == 0 then
		result = '__DISAMBIG__' .. frame:extensionTag{
			name = 'indicator',
			content = string.format( '[[File:Disambig.svg|20px|link=%s|%s]]', DISAMBIG_HELP, DISAMBIG_HELP_LABEL ),
			args = { name = '0-disambig' },
		} .. result
	end
	
	return frame:extensionTag{
		name = 'templatestyles',
		args = { src = templateStylesPage },
	} .. result
end

function p.main( frame )
	if mw.isSubsting() then
		return p._substing( frame )
	end
	
	local args = getArgs( frame )
	return p._main( frame, args ) .. p._checkErrors( frame, args )
end

-- Renders alias templates with substing capabilities
function p.alias( frame )
	local args = getArgs( frame )
	local template = args[ '$template' ]
	
	args[ '$template' ] = nil
	if mw.isSubsting() then
		return p._substing( frame, args, 'Template:' .. template )
	end
	
	return frame:expandTemplate{
		title = template,
		args = { args[ 1 ] },
	} .. p._checkErrors( frame, args )
end

-- Renders parts of {{Category disambiguation}} template
function p._category( frame, args )
	if frame == nil then
		frame = mw.getCurrentFrame()
	end
	
	if isEmpty( args[ 2 ] ) and isEmpty( args[ 4 ] ) then
		return error( CATEGORY_ERROR )
	end
	
	local params = {}
	for key, val in pairs( args ) do
		if type( key ) == 'number' then
			if isEmpty( params[ key - 1 ] ) then
				params[ key - 1 ] = params[ key - 1 ] or ''
			end
			params[ key ] = val
		end
	end
	
	local result = mw.html.create( 'ul' )
	for i, catName in ipairs( params ) do
		if i % 2 == 0 and not isEmpty( catName ) then
			local li = result:tag( 'li' )

			local label = args[ i - 1 ]
			if not isEmpty( label ) then
				li:wikitext( string.format( '<b>%s</b> — %s ', label, IN_CATEGORY ) )
			end
			li:wikitext( string.format( '<b>[[:Category:%s|%s]]</b>', catName, catName ) )
		end
	end
	
	return tostring( result )
end

function p.category( frame )
	if mw.isSubsting() then
		return p._substing( frame )
	end
	
	return p._category( frame, getArgs( frame ) )
end

return p