Дакумэнтацыю да гэтага модуля можна стварыць у Модуль:Партал/Дакумэнтацыя

--[==[ Гэты модуль — перапісаны на Lua код шаблёну {{Партал}}.
-- Мае дзьве вонкавыя функцыі:
-- p.portal, якая складае сьпіс парталаў;
-- p.image, якая ўтварае назву выяву для асобна ўзятага парталу.

-- Зьвесткі пра выявы парталаў захоўваюцца ў падмодулях [[Модуль:Партал/выявы]], пералічаных ніжэй:
-- [[Модуль:Партал/выявы/а]]		— для парталаў, якія пачынаюцца зь літары «А».
-- [[Модуль:Партал/выявы/б]]		— для парталаў, якія пачынаюцца зь літары «Б».
-- [[Модуль:Партал/выявы/в]]		— для парталаў, якія пачынаюцца зь літары «В».
-- [[Модуль:Партал/выявы/г]]		— для парталаў, якія пачынаюцца зь літары «Г».
-- [[Модуль:Партал/выявы/д]]		— для парталаў, якія пачынаюцца зь літары «Д».
-- [[Модуль:Партал/выявы/е]]		— для парталаў, якія пачынаюцца зь літары «Е».
-- [[Модуль:Партал/выявы/ё]]		— для парталаў, якія пачынаюцца зь літары «Ё».
-- [[Модуль:Партал/выявы/ж]]		— для парталаў, якія пачынаюцца зь літары «Ж».
-- [[Модуль:Партал/выявы/з]]		— для парталаў, якія пачынаюцца зь літары «З».
-- [[Модуль:Партал/выявы/і]]		— для парталаў, якія пачынаюцца зь літары «І».
-- [[Модуль:Партал/выявы/к]]		— для парталаў, якія пачынаюцца зь літары «К».
-- [[Модуль:Партал/выявы/л]]		— для парталаў, якія пачынаюцца зь літары «Л».
-- [[Модуль:Партал/выявы/м]]		— для парталаў, якія пачынаюцца зь літары «М».
-- [[Модуль:Партал/выявы/н]]		— для парталаў, якія пачынаюцца зь літары «Н».
-- [[Модуль:Партал/выявы/о]]		— для парталаў, якія пачынаюцца зь літары «О».
-- [[Модуль:Партал/выявы/п]]		— для парталаў, якія пачынаюцца зь літары «П».
-- [[Модуль:Партал/выявы/р]]		— для парталаў, якія пачынаюцца зь літары «Р».
-- [[Модуль:Партал/выявы/с]]		— для парталаў, якія пачынаюцца зь літары «С».
-- [[Модуль:Партал/выявы/т]]		— для парталаў, якія пачынаюцца зь літары «Т».
-- [[Модуль:Партал/выявы/у]]		— для парталаў, якія пачынаюцца зь літары «У».
-- [[Модуль:Партал/выявы/ф]]		— для парталаў, якія пачынаюцца зь літары «Ф».
-- [[Модуль:Партал/выявы/х]]		— для парталаў, якія пачынаюцца зь літары «Х».
-- [[Модуль:Партал/выявы/ц]]		— для парталаў, якія пачынаюцца зь літары «Ц».
-- [[Модуль:Партал/выявы/ч]]		— для парталаў, якія пачынаюцца зь літары «Ч».
-- [[Модуль:Партал/выявы/ш]]		— для парталаў, якія пачынаюцца зь літары «Ш».
-- [[Модуль:Партал/выявы/э]]		— для парталаў, якія пачынаюцца зь літары «Э».
-- [[Модуль:Партал/выявы/ю]]		— для парталаў, якія пачынаюцца зь літары «Ю».
-- [[Модуль:Партал/выявы/я]]		— для парталаў, якія пачынаюцца зь літары «Я».
-- [[Модуль:Партал/выявы/іншыя]]	— для парталаў, якія пачынаюцца зь якіх-кольвек іншых сымбаляў.
--                                    Сюды ўлучаюцца і лічбы, і літары лацінскага альфабэту, і дыякрытычныя знакі.
-- [[Модуль:Партал/выявы/альт]]		— для альтэрнатыўных назваў існых парталаў. Зь любых пачатковых літараў.
--
-- Падстаронкі са зьвесткамі пра выявы падзеленыя па першай літары дзеля зьмяншэньня нагрузкі на сэрвэр у выпадку зьмяненьняў ці дапаўненьняў.
-- Зьмяшчэньне ўсіх выяваў на падстаронцы [[Модуль:Партал/выявы]] ў выпадку абнаўленьня аднае з выяваў
-- прыводзіла б да перазагрузкі ўсіх старонак, якія карыстаюцца гэтым модулем.
]==]

local p = {}

local trackingEnabled = true

local templatestyles = 'Модуль:Партал/styles.css'

local yesno = require('Модуль:ТакНе')

-- Праверыць, ці патрэбнае адсочваньне ў гэтай прасторы назваў
-- Вяртае ісьціну, калі старонка не належыць „забароненым“ прасторам
local function checkTrackingNamespace()
	local thisPage = mw.title.getCurrentTitle()
	if (thisPage.namespace == 1) -- Абмеркаваньне
		or (thisPage.namespace == 2) -- Удзельнік
		or (thisPage.namespace == 3) -- Гутаркі ўдзельніка
		or (thisPage.namespace == 5) -- Абмеркаваньне Вікіпэдыі
		or (thisPage.namespace == 7) -- Абмеркаваньне файла
		or (thisPage.namespace == 11) -- Абмеркаваньне шаблёну
		or (thisPage.namespace == 15) -- Абмеркаваньне катэгорыі
		or (thisPage.namespace == 101) -- Абмеркаваньне парталу
		or (thisPage.namespace == 109) -- Абмеркаваньне кнігі
		or (thisPage.namespace == 118) -- Чарнавік
		or (thisPage.namespace == 119) -- Абмеркаваньне чарнавіку
		or (thisPage.namespace == 829) -- Абмеркаваньне модулю
		then
		return false
	end
	return true
end

-- Праверыць, ці патрэбнае адсочваньне гэтай назвы старонкі
-- Вяртае хлусьню, калі назва старонкі падпадае пад „забароненую“ маску
-- У іншых выпадках — ісьціна
local function checkTrackingPagename()
	local thisPage = mw.title.getCurrentTitle()
	local thisPageLC = mw.ustring.lower(thisPage.text)
	if (string.match(thisPageLC, "/архіў") ~= nil) then
		return false
	end
	if (string.match(thisPageLC, "/дакумэнтацыя") ~= nil) then
		return false
	end
	if (string.match(thisPageLC, "/тэст") ~= nil) then
		return false
	end
	return true
end


local function matchImagePage(s)
	-- Шукае адпаведную падстаронку выяваў па назьве парталу
	-- і першай літары ягонай назвы ў ніжнім рэгістры.
	if type(s) ~= 'string' or #s < 1 then return end
	local firstLetter = mw.ustring.sub(s, 1, 1)
	local imagePage
	if mw.ustring.find(firstLetter, '^[а-я]') then
		imagePage = 'Модуль:Партал/выявы/' .. firstLetter
	else
		imagePage = 'Модуль:Партал/выявы/іншыя'
	end
	return mw.loadData(imagePage)[s]
end

local function getAlias(s)
	-- Вяртае альтэрнатыву з падстаронкі альтэрнатыўных зьвестак пра выявы.
	local aliasData = mw.loadData('Модуль:Партал/выявы/альт')
	for portal, aliases in pairs(aliasData) do
		for _, alias in ipairs(aliases) do
			if alias == s then
				return portal
			end
		end
	end
end

local function getImageName(s)
	-- Вяртае назву выявы для ўваходнага радку.
	local default = 'Portal-puzzle.svg|link=|alt='
	if type(s) ~= 'string' or #s < 1 then
		return default
	end
	s = mw.ustring.lower(s)
	return matchImagePage(s) or matchImagePage(getAlias(s)) or default
end

local function checkPortalExists(portal)
	return not (mw.title.makeTitle(100, portal).id == 0)
end

function p._portal(portals, args)
	-- Будуе рамку парталу для шаблёну {{Партал}}.
	local root = mw.html.create('div')
		:attr('role', 'navigation')
		:attr('aria-label', 'Portals')
		:addClass('noprint portal plainlist notheme')
		:addClass(args.left and 'tleft' or 'tright')
		:css('margin', args.margin or nil)
		:newline()

	-- Дапомна адсочваньне ўключанае.
	-- Адключаецца, калі выконваецца адна з такіх умоваў:
	-- 1) Парамэтар "tracking" зададзены = 'no, 'n' або 'false'
	-- 2) Старонка не прайшла праверку прасторы назваў у checkTrackingNamespace()
	-- 3) Старонка не прайшла праверку маскі назвы ў checkTrackingPagename()
	trackingEnabled = yesno(args.tracking, trackingEnabled)
	if (checkTrackingNamespace() == false) then
		trackingEnabled = false
	end
	if (checkTrackingPagename() == false) then
		trackingEnabled = false
	end

	-- Калі парталы не зададзеныя, выводзіць памылку і дадае старонку ў катэгорыю адсочваньня.
	if not portals[1] then
		if yesno(args.nominimum) then
		-- калі парамэтар nominimum зададзены yes (ці падобнае), апусьціць папярэджаньне

		else
			root:wikitext('<strong class="error">Не зададзеныя парталы: пазначце хаця б адзін</strong>')
		end
		if (trackingEnabled) then
			root:wikitext('[[Катэгорыя:Вікіпэдыя:Шаблён «Партал» без парамэтраў]]')
		end
		return tostring(root)
	end
	
	-- шукае няісныя парталы; калі такія знойдзеныя, то выдаляе іх з вакна вываду.
	--- Калі redlinks=yes, то не выдаляе
	local portallen = #portals
	-- перабірае сьпіс ззаду наперад, каб упэўніцца, што ніякія парталы не прапушчаныя
	-- (table.remove таксама ідзе па адваротным сьпісе парталаў, так што наступны партал не праверыцца, калі ісьці ў простым парадку.
	-- праход у адваротным кірунку дазваляе абысьці гэтую праблему
	for i=portallen,1,-1 do
		-- выкарыстаньнем pcall адсочваем любыя памылкі, якія могуць здарыцца
		-- пры спробе пошуку старонак з хібнай назвай
		-- калі pcall вяртае ісьціну, тады перазапусьціць функцыю для праверкі, ці існуе старонка
		if not pcall(checkPortalExists, portals[i]) or not checkPortalExists(portals[i]) then
			-- Калі мы тут, значыць знайшоўся чырвоны партал
			if yesno(args.redlinks) or (args.redlinks == 'include') then
				-- калі redlinks зададзены yes (ці падобныя), дадаць катэгорыю адсочваньня
				-- і выйсьці з цыклю перад тым, як партал будзе выдалены з зьпісу
				if (trackingEnabled) then
					root:wikitext('[[Катэгорыя:Вікіпэдыя:Шаблён «Партал» з чырвонымі спасылкамі]]')
				end
				break
			end
			-- прыбраць партал (не спрацуе, калі redlinks=yes)
			table.remove(portals,i)
		end
	end
	
	-- калі даўжыня табліцы зьмянілася, значыць, зь яе былі выдаленыя радкі,
	-- то бок парталы. У такім разе дадаць катэгорыю адсочваньня
	if not (portallen == #portals) then
		if (trackingEnabled) then
			if #portals == 0 then
				return '[[Катэгорыя:Вікіпэдыя:Шаблён «Партал» з чыста чырвонымі спасылкамі]]'
			else
				root:wikitext('[[Катэгорыя:Вікіпэдыя:Шаблён «Партал» з чырвонымі спасылкамі]]')
			end
		end
	end

	-- Запачаткоўвае сьпіс. Адпавядае пачатку вікі-табліцы ў старым [[Шаблён:Партал]].
	local listroot = root:tag('ul')
		:css('width', type(args.boxsize) == 'string' and (args.boxsize .. 'px') or nil)

	-- Выводзіць парталы, зададзеныя ў пазыцыйных аргумэнтах.
	for _, portal in ipairs(portals) do
		local image = getImageName(portal)

		-- Генэруе html-код для выявы і назвы парталу.
		listroot
			:newline()
			:tag('li')
				:tag('span')
					:wikitext(string.format('[[Файл:%s|32x28пкс|class=noviewer]]', image))
					:done()
				:tag('span')
					:wikitext(string.format('Вікіпэдыя мае партал\n«[[Партал:%s|%s]]»', portal, portal, args['break'] and '<br />' or ' '))
	end
	return tostring(root)
end

function p._image(portals)
	-- Абгортачная функцыя, каб getImageName() была дасяжная праз #invoke.
	local name = getImageName(portals[1])
	return name:match('^(.-)|') or name -- ДАРАБІЦЬ: зрабіць больш элегантны спосаб аддзяляць рамкі і інш. ад назвы выявы
end

local function getAllImageTables()
	-- Вяртае масіў з усімі падстаронкамі выяваў (без альтэрнатываў), загружаны mw.loadData.
	local images = {}
	for i, subpage in ipairs{'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'і', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'э', 'ю', 'я', 'іншыя'} do
		images[i] = mw.loadData('Модуль:Партал/выявы/' .. subpage)
	end
	return images
end

function p._displayAll(portals, args)
	-- Выводзіць усе парталы, якія маюць зьвязаныя выявы. Функцыя прызначаная не для артыкулаў, а толькі для службовага выкарыстаньня.
	local lang = mw.language.getContentLanguage()
	local count = 1
	for _, imageTable in ipairs(getAllImageTables()) do
		for portal in pairs(imageTable) do
			portals[count] = lang:ucfirst(portal)
			count = count + 1
		end
	end
	return p._portal(portals, args)
end

function p._imageDupes()
	-- Шукае падстаронкі на прадмет дублікатаў выяваў. Калі такія выявы існуюць, гэта не абавязкова памылка,
	-- бо розныя парталы могуць карыстацца аднымі і тымі ж выявамі. Аднак функцыя дапамагае вызначыць выявы,
	-- якія варта перамясьціць на падстаронку альтэрнатыўных назваў.
	local exists, dupes = {}, {}
	for _, imageTable in ipairs(getAllImageTables()) do
		for portal, image in pairs(imageTable) do
			if not exists[image] then
				exists[image] = portal
			else
				table.insert(dupes, string.format('Выява «[[:Файл:%s|%s]]» выкарыстоўваецца ў парталах «%s» і «%s».', image, image, exists[image], portal))
			end
		end
	end
	if #dupes < 1 then
		return 'Дубляваныя выявы адсутнічаюць.'
	else
		return 'Знойдзеныя такія дубляваныя выявы:\n* ' .. table.concat(dupes, '\n* ')
	end
end

local function processPortalArgs(args)
	-- Апрацоўвае табліцу аргумэнтаў і вяртае дзьве табліцы: масіў назваў парталаў дзеля апрацоўкі ў ipairs, і табліцу
	-- найменных аргумэнтаў, якія вызначаюць налады стыляў і інш. Карыстацца ipairs даводзіцца, паколькі нам патрэбныя
        -- сьпісы ўсіх парталаў у тым парадку, у якім яны былі перададзеныя ў шаблён, а таксама неабходна яўна зьвяртацца да
        -- пазыцыйных аргумэнтаў, напрыклад {{Партал|2=Хрысьціянства}}. Паводзіны ipairs у выпадку, калі прысутнічаюць пустыя
        -- значэньні, нявызначаныя, таму мусім пераканацца, што ўсе яны выдаленыя.
	args = type(args) == 'table' and args or {}
	local portals = {}
	local namedArgs = {}
	for k, v in pairs(args) do
		if type(k) == 'number' and type(v) == 'string' then -- Правяраем, ці ня маем нерадковых назваў парталаў.
			table.insert(portals, k)
		elseif type(k) ~= 'number' then
			namedArgs[k] = v
		end
	end
	table.sort(portals)
	for i, v in ipairs(portals) do
		portals[i] = args[v]
	end
	return portals, namedArgs
end

local function makeWrapper(funcName)
	-- Апрацоўвае вонкавыя аргумэнты і перадае ў іншыя функцыі.
	return function (frame)
		-- Пры выкліку праз #invoke узяць аргумэнты, перададзеныя ў шаблёне
		-- альбо ў #invoke. Інакш лічым, што аргумэнты перададзеныя наўпрост
		-- з адладачнай кансолі ці зь іншага Lua-модулю.
		local origArgs
		if type(frame.getParent) == 'function' then
			origArgs = frame:getParent().args
			for k, v in pairs(frame.args) do
				origArgs = frame.args
				break
			end
		else
			origArgs = frame
		end
		-- Абразае пропусты і выдаляе пустыя аргумэнты.
		local args = {}
		for k, v in pairs(origArgs) do
			if type(v) == 'string' then
				v = mw.text.trim(v)
			end
			if v ~= '' then
				args[k] = v
			end
		end
		
		local results = ''
		if funcName == '_portal' or funcName == '_displayAll' then
			results = frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles} }
		end
		return results .. p[funcName](processPortalArgs(args)) -- перадае ў функцыю дзьве табліцы: масіў назваў парталаў і табліцу найменаваных аргумэнтаў.
	end
end

for _, funcName in ipairs{'portal', 'image', 'imageDupes', 'displayAll'} do
	p[funcName] = makeWrapper('_' .. funcName)
end

return p