Для документации этого модуля может быть создана страница Модуль:Родственные проекты/doc

local p = {};

-- Configuration data
local config = require( 'Module:Родственные_проекты/config' );

-- Modules 
local getArgs = require( 'Module:Arguments' ).getArgs;
local errorMsg = require( 'Module:Error' ).error;
local yesno = require( 'Module:Yesno' );

-- Variables
local modules = {};

-- Helpers
local function formatProject( frame, args, item )
	local out = '';
	local context = frame;

	local entityId
	if args.from and args.from ~= '' then
		entityId = args.from
	else
		entityId = mw.wikibase.getEntityIdForCurrentPage()
	end

	if entityId and item.sitelink then
		item.value = mw.wikibase.getSitelink( entityId, item.sitelink )
	elseif item[ 'module' ] and item[ 'function' ] then
		if modules[ item[ 'module' ] ] == nil then
			modules[ item[ 'module' ] ] = require( 'Module:' .. item[ 'module' ] );
		end

		context.args = item[ 'args' ] or {}
		context.args.from = entityId

		item.wikiValue = modules[ item[ 'module' ] ][ item[ 'function' ] ]( context );
		-- add wiki prefix
		if item.wikiValue ~= nil and item.wikiValue ~= '' and item.wikiPrefix ~= nil and item.wikiPrefix ~= '' then
			item.wikiValue = item.wikiPrefix.. ':' .. item.wikiValue;
		end
		-- override value if local value does not exist
		if item.value == nil or item.value == '' then
			item.value = item.wikiValue;
		end
	end
	
	if item.value ~= nil and item.value ~= '' then
		-- replace variables
		if item.title ~= nil and item.title ~= '' then
			item.title = string.gsub( item.title, '%$1', item.value );
		end
		-- format prefix
		if string.find( item.value, '^' .. item.prefix .. ':' ) ~= nil then
			item.prefixFormatted = '';
		else
			item.prefixFormatted = item.prefix .. ':';
		end
		-- format image
		if item.imageTemplate ~= nil and item.imageTemplate ~= '' then
			item.imageFormatted = frame:expandTemplate{ title = item.imageTemplate, args = { item.value, size = config.params.itemImageSize } };
		elseif item.image ~= nil and item.image ~= '' then
			item.imageFormatted = '[[File:' .. item.image .. '|link=|alt=|' .. config.params.itemImageSize .. 'px]]';
		end
		-- format wikilink
		out = out .. '[[' .. item.prefixFormatted .. item.value .. '|' .. item.title .. ']]';
	end
	
	return out;
end

local function getParamsList( frame, args, list )
	local result = {};
	
	for i, item in ipairs( list ) do
		local value = args[item.id];
		if value == nil or value ~= '-' then
			item.localValue = value;
			item.value = value;
			item.formatted = formatProject( frame, args, item );
			if item.formatted ~= '' then
				table.insert( result, item );
			end
		end
	end
	
	return result;
end

-- Renders
local function renderProjects( frame, list )
	local res = mw.html.create();
	
	for i, item in ipairs( list ) do
		-- wrapper
		local li = res:tag( 'li' )
			:addClass( config.params.tsClass .. '-item' );
		-- class
		local class = '';
		if item.name == 'project' or item.name == 'portal' then
			class = item.name .. '-box';
		else
			class = item.name .. '-ref';
		end
		-- image
		if item.imageFormatted ~= nil and item.imageFormatted ~= '' then
			li:tag( 'span' )
				:addClass( config.params.tsClass .. '-image' )
				:wikitext( item.imageFormatted );
		end
		-- formatted value
		li:tag( 'span' )
			:addClass( config.params.tsClass .. '-label' )
			:addClass( class )
			:wikitext( item.formatted );
	end
	
	return res;
end

local function render( frame, title, interprojects, otherprojects )
	local templateStyles = frame:extensionTag{ name = 'templatestyles', args = { src = config.params.templateStyles } };
	local interprojectsFormatted = renderProjects( frame, interprojects );
	local otherprojectsFormatted = renderProjects( frame, otherprojects );
	-- wrapper
	local res = mw.html.create()
		:wikitext( templateStyles );
	local body = res:tag( 'div' )
		:addClass( config.params.tsClass )
		:addClass( config.params.bodyClass )
		:attr( 'role', 'navigation' );
	-- render title
	if table.getn( interprojects ) > 1  and ( title == nil or title == '' ) then
		title = config.title['default'];
	end
	if title ~= nil and title ~= '' then
		body:attr( 'aria-labelledby', mw.uri.anchorEncode( title ) );
		body:tag( 'div' )
			:addClass( config.params.tsClass .. '-header' )
			:attr( 'id', mw.uri.anchorEncode( title ) )
			:wikitext( "'''" .. title .. "'''" );
	else
		body:attr( 'aria-label', config.title['default'] );
	end
	-- render inter projects
	if table.getn( interprojects ) > 0 then
		body:tag( 'ul' )
			:node( interprojectsFormatted );
	end
	-- render separator
	if table.getn( interprojects ) > 0 and table.getn( otherprojects ) > 0 then
		body:tag( 'hr' );
	end
	-- render other projects
	if table.getn( otherprojects ) > 0 then
		body:tag( 'ul' )
			:node( otherprojectsFormatted );
	end

	return res;
end

-- Categories
local function formatCategory( value )
	return '[[Category:' .. value .. ']]';
end

local function formatCategories( frame, list )
	local categories = {};
	
	for i, item in ipairs( list ) do
		local categoryItem = config.categories[ item.name ];
		if categoryItem ~= nil then
			if categoryItem['local'] ~= nil and item.localValue ~= nil and item.localValue ~= '' then
				local category = formatCategory( categoryItem['local'] )
				table.insert( categories, category );
			elseif categoryItem['default'] ~= nil then
				local category = formatCategory( categoryItem['default'] )
				table.insert( categories, category );
			end
			-- when local value is equal to wikidata
			if categoryItem['equal'] ~= nil 
				and item.localValue ~= nil and item.localValue ~= '' 
				and item.wikiValue ~= nil and item.wikiValue ~= ''
				and item.localValue == item.wikiValue
			then
				local category = formatCategory( categoryItem['equal'] )
				table.insert( categories, category );
			end
			-- when local value is not equal to wikidata
			if categoryItem['not-equal'] ~= nil 
				and item.localValue ~= nil and item.localValue ~= '' 
				and item.wikiValue ~= nil and item.wikiValue ~= ''
				and item.localValue ~= item.wikiValue
			then
				local category = formatCategory( categoryItem['not-equal'] )
				table.insert( categories, category );
			end
			-- category for non existing pages
			if categoryItem['not-exists'] ~= nil then
				local page = mw.title.new( item.value, item.prefix );
				if not page.exists then
					local category = formatCategory( categoryItem['not-exists'] )
					table.insert( categories, category );
				end
			end
		end
	end
	
	return table.concat( categories );
end

local function renderCategories( frame, interprojects, otherprojects )
	-- wrapper
	local res = mw.html.create();
	-- format iterprojects categories
	local interprojectsCategories = formatCategories( frame, interprojects );
	res:wikitext( interprojectsCategories );
	-- format otherprojects categories
	local otherprojectsCategories = formatCategories( frame, otherprojects );
	res:wikitext( otherprojectsCategories );
	-- service categories
	if next( interprojects ) == nil and next( otherprojects ) == nil then
		local category = formatCategory( config.categories['empty'] );
		res:wikitext( category );
	end
	
	return res;
end

-- Preview
local function renderPreview( frame )
	local res = mw.html.create();
	if config.preview ~= nil and config.preview['empty'] ~= nil and config.preview['empty'] ~= '' then
		local message = errorMsg( {
			tag = 'p',
			message = config.preview['empty']
		} );
		res:wikitext( message );
	end
	return res;
end;

-- Main
function p.main( frame )
	local args = getArgs( frame, { wrappers = config.params.wrappers } );
	local nocat = yesno( args['nocat'] );
	-- title parameter
	local title = '';
	for i, item in ipairs( config.title.id ) do
		if args[item] ~= nil and args[item] ~= '' then
			title = args[item];
		end
	end
	if title ~= nil and title ~= '' then
		title = title .. ':'
	end
	-- get parameters list
	local interprojects = getParamsList( frame, args, config['interprojects'] );
	local otherprojects = getParamsList( frame, args, config['otherprojects'] );
	-- wrapper
	local res = mw.html.create();
	-- render body
	if next( interprojects ) ~= nil or next( otherprojects ) ~= nil then
		local body = render( frame, title, interprojects, otherprojects );
		res:node( body );
	elseif config.params.showPreview and frame:preprocess( '{{REVISIONID}}' ) == '' then
		local preview = renderPreview( frame );
		res:node( preview );
	end
	-- render category list
	if not nocat then
		local pageTitle = mw.title.getCurrentTitle();
		if pageTitle.namespace == 0 then
			local categories = renderCategories( frame, interprojects, otherprojects );
			res:node( categories );
		end
	end
	
	return tostring( res );
end

return p;