Модуль:Arguments

З пляцоўкі Вікікнігі

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

-- Гэты модуль забяспечвае лёгкую апрацоўку аргументаў, якія перадаюцца 
-- Scribunto з #invoke. Мяркуецца, што гэты модуль будзе выкарыстоўвацца іншымі  
-- модулямі на Lua, і не павінен выклікацца з #invoke прама.

local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType

local arguments = {}

-- Стварыць чатыры розныя функцыі tidyVal, так што нам не трэба правяраць 
-- уласцівасці кожны раз пры выкліку яе.

local function tidyValDefault(key, val)
	if type(val) == 'string' then
		val = val:match('^%s*(.-)%s*$')
		if val == '' then
			return nil
		else
			return val
		end
	else
		return val
	end
end

local function tidyValTrimOnly(key, val)
	if type(val) == 'string' then
		return val:match('^%s*(.-)%s*$')
	else
		return val
	end
end

local function tidyValRemoveBlanksOnly(key, val)
	if type(val) == 'string' then
		if val:find('%S') then
			return val
		else
			return nil
		end
	else
		return val
	end
end

local function tidyValNoChange(key, val)
	return val
end

local function matchesTitle(given, title)
	local tp = type( given )
	return (tp == 'string' or tp == 'number') and mw.title.new( given ).prefixedText == title
end

local translate_mt = { __index = function(t, k) return k end }

function arguments.getArgs(frame, options)
	checkType('getArgs', 1, frame, 'table', true)
	checkType('getArgs', 2, options, 'table', true)
	frame = frame or {}
	options = options or {}

	--[[
	-- Пачаць пераклад аргументаў.
	--]]
	options.translate = options.translate or {}
	if getmetatable(options.translate) == nil then
		setmetatable(options.translate, translate_mt)
	end
	if options.backtranslate == nil then
		options.backtranslate = {}
		for k,v in pairs(options.translate) do
			options.backtranslate[v] = k
		end
	end
	if options.backtranslate and getmetatable(options.backtranslate) == nil then
		setmetatable(options.backtranslate, {
			__index = function(t, k)
				if options.translate[k] ~= k then
					return nil
				else
					return k
				end
			end
		})
	end

	--[[
	-- Узяць табліцы аргументаў. Калі нам перадаюцца дзейны фрэйм аб'ект, узяць 
	-- аргументы фрэйма (fargs) і агрументы бацькоўскага фрэйма (pargs), у 
	-- залежнасці ад усталяваных наладак і ад даступнасці бацькоўскага фрэйма. 
	-- Калі нам не перададзены дзейны фрэйм аб'ект, значыць выклік ідзе з іншага
	--  модуля на Lua ці з кансолі адладкі, так што мяркуем, што нам перадаюць 
	-- табліцу аргументаў напрамую, і прысвойваем яе новай зменнай (luaArgs).
	--]]
	local fargs, pargs, luaArgs
	if type(frame.args) == 'table' and type(frame.getParent) == 'function' then
		if options.wrappers then
			--[[
			-- Магчымасць абгортак прымушае Module:Arguments звяртацца да  in
			-- аргументаў або ў табліцы аргументаў фрэйма, або у табліцы 
			-- бацькоўскага аргумента, але не ў абодвух адначасова. Гэта
			-- значыць, што карыстальнікі могуць або выкарыстоўваць сінтаксіс 
			-- #invoke, або шаблон-абгортку без страты эфектыўнасці, звязанай са
			-- зваротам да аргументаў у фрэйме і бацькоўскім фрэйме адначасова.
			-- Module:Arguments будзе звяртацца да аргументаў у бацькоўскім
			--  фрэйме, калі ён знойдзе загаловак бацькоўскага фрэйма ў passed
			--  options.wrapper; у адваротным выпадку ён будзе звяртацца да
			--  аргументаў у аб'екце фрэйма, перададзенага getArgs.
			--]]
			local parent = frame:getParent()
			if not parent then
				fargs = frame.args
			else
				local title = parent:getTitle():gsub('/sandbox$', '')
				local found = false
				if matchesTitle(options.wrappers, title) then
					found = true
				elseif type(options.wrappers) == 'table' then
					for _,v in pairs(options.wrappers) do
						if matchesTitle(v, title) then
							found = true
							break
						end
					end
				end

				-- Праверым, ці няпраўда, асабліва тут, так што nil (па
				-- змоўчанні) дзейнічае як праўда.
				if found or options.frameOnly == false then
					pargs = parent.args
				end
				if not found or options.parentOnly == false then
					fargs = frame.args
				end
			end
		else
			-- options.wrapper не зададзены, так што правяраем іншыя магчымасці.
			if not options.parentOnly then
				fargs = frame.args
			end
			if not options.frameOnly then
				local parent = frame:getParent()
				pargs = parent and parent.args or nil
			end
		end
		if options.parentFirst then
			fargs, pargs = pargs, fargs
		end
	else
		luaArgs = frame
	end

	-- Задаць набор паслядоўнасці аргументаў табліц. Калі зменныя роўныя
	-- nil, у табліцу не будзе дадавацца нічога, што дазволіць пазбегнуць
	-- канфліктаў паміж аргументамі фрэйма/бацькоўскага фрэйма і аргументамі
	-- Lua.
	local argTables = {fargs}
	argTables[#argTables + 1] = pargs
	argTables[#argTables + 1] = luaArgs

	--[[
	-- Стварць функцыю tidyVal. Калі яна была зададзеная карыстальнікам, мы 
	-- выкарыстоўваем яе; у адваротным выпадку мы выбіраем адну з чатырох
	-- функцый у залежнасці ад выбраных параметраў. Гэта робіцца, каб нам не
	-- давялося звяртацца да табліцы параметраў кожны раз пры выкліку функцыі.
	--]]
	local tidyVal = options.valueFunc
	if tidyVal then
		if type(tidyVal) ~= 'function' then
			error(
				"bad value assigned to option 'valueFunc'"
					.. '(function expected, got '
					.. type(tidyVal)
					.. ')',
				2
			)
		end
	elseif options.trim ~= false then
		if options.removeBlanks ~= false then
			tidyVal = tidyValDefault
		else
			tidyVal = tidyValTrimOnly
		end
	else
		if options.removeBlanks ~= false then
			tidyVal = tidyValRemoveBlanksOnly
		else
			tidyVal = tidyValNoChange
		end
	end

	--[[
	-- Задаць табліцы аргументаў, metaArgs ды nilArgs. Да аргументаў будзе 
	-- доступ з функцый, metaArgs будуць утрымліваць сапраўдныя аргументы. 
	-- Пустыя аргументы захоўваюцца ў nilArgs, а метатабліца злучаная з імі 
	-- ўсімі адначасова.
	--]]
	local args, metaArgs, nilArgs, metatable = {}, {}, {}, {}
	setmetatable(args, metatable)

	local function mergeArgs(tables)
		--[[
		-- Прымае некалькі табліц у якасці ўваходных і аб'ядноўвае іх ключы і
		-- значэнні ў адну табліцу. Калі значэнне ўжо ёсць, яно не сціраецца;
		-- табліцы, створаныя раней, маюць паслядоўнасць. Мы таксама запамінаем 
		-- пустыя значэнні, якія могуць быць замененыя, калі яны 's' (soft).
		--]]
		for _, t in ipairs(tables) do
			for key, val in pairs(t) do
				if metaArgs[key] == nil and nilArgs[key] ~= 'h' then
					local tidiedVal = tidyVal(key, val)
					if tidiedVal == nil then
						nilArgs[key] = 's'
					else
						metaArgs[key] = tidiedVal
					end
				end
			end
		end
	end

	--[[
	-- Вызначыць паводзіны метатабліцы. Аргументы запамінаюцца ў табліцы
	-- metaArgs, і чытаюцца з табліцы аргументаў толькі аднойчы. Чытанне
	-- аргументаў з табліцы аргументаў - крок, які займае больш за ўсё рэсурсаў
	-- у гэтым модулі, так што мы спрабуем пазбегнуць гэтага, дзе магчыма. Таму 
	-- пустыя аргументы таксама запамінаюцца ў табліцы nilArgs. Таксама мы
	-- захоўваем запіс у метатабліцы, калі адбываецца зварот да пар і ipairs,
	-- так што мы не выконваем пары і ipairs у табліцах аргументаў больш за
	-- адзін раз. Мы таксама не выконваем ipairs на fargs і pargs, калі пары ўжо
	-- выконваліся, таму што ўсе аргументы ўжо капіраваліся наверх.
	--]]

	metatable.__index = function (t, key)
		--[[
		-- Чытае аргумент, калі табліца аргументаў індэксаваная. Спачатку мы
		-- правяраем, ці запісанае значэнне, і калі не, то мы спрабуем прачытаць
		-- яго з табліц аргументаў. Калі мы правяраем запіс, нам трэба праверыць
		-- metaArgs перад nilArgs, таму што і адны, і другія могуць быць
		-- непустыя адначасова. Калі аргумента няма ў metaArgs, мы таксама
		-- правяраем, ці выконваліся ўжо пары. Калі так, то вяртаем пустое
		-- значэнне. Гэта робіцца, таму што ўсе аргументы ўжо скапіраваныя ў
		-- metaArgs фунцыяй mergeArgs, што значыць, што усе астатнія аргументы
		-- павінны быць пустыя.
		--]]
		if type(key) == 'string' then
			key = options.translate[key]
		end
		local val = metaArgs[key]
		if val ~= nil then
			return val
		elseif metatable.donePairs or nilArgs[key] then
			return nil
		end
		for _, argTable in ipairs(argTables) do
			local argTableVal = tidyVal(key, argTable[key])
			if argTableVal ~= nil then
				metaArgs[key] = argTableVal
				return argTableVal
			end
		end
		nilArgs[key] = 'h'
		return nil
	end

	metatable.__newindex = function (t, key, val)
		-- Гэтая функцыя выклікаецца, калі модуль спрабуе дадаць новае значэнне
		-- да табліцы args ці спрабуе змяніць значэнне, якое ўжо існуе.
		if type(key) == 'string' then
			key = options.translate[key]
		end
		if options.readOnly then
			error(
				'could not write to argument table key "'
					.. tostring(key)
					.. '"; the table is read-only',
				2
			)
		elseif options.noOverwrite and args[key] ~= nil then
			error(
				'could not write to argument table key "'
					.. tostring(key)
					.. '"; overwriting existing arguments is not permitted',
				2
			)
		elseif val == nil then
			--[[
			-- Калі аргумент трэба сцерці, нам трэба сцерці значэнне ў metaArgs,
			-- так што __index, __pairs і __ipairs не будуць выкарыстоўваць
			-- значэнне, якое існавала раней, калі яно было; і нам таксама трэба
			-- запомніць пустое значэнне ў nilArgs, так што да значэнне не будзе
			-- чытацца з табліца аргументаў, калі да яго звернуцца зноў.
			--]]
			metaArgs[key] = nil
			nilArgs[key] = 'h'
		else
			metaArgs[key] = val
		end
	end

	local function translatenext(invariant)
		local k, v = next(invariant.t, invariant.k)
		invariant.k = k
		if k == nil then
			return nil
		elseif type(k) ~= 'string' or not options.backtranslate then
			return k, v
		else
			local backtranslate = options.backtranslate[k]
			if backtranslate == nil then
				-- Прапусціць гэта. Гэта канец выкліку, так што не будзе
				-- перапаўнення стэка
				return translatenext(invariant)
			else
				return backtranslate, v
			end
		end
	end

	metatable.__pairs = function ()
		-- Выклікаецца, калі пары выконваюцца ў табліцы args.
		if not metatable.donePairs then
			mergeArgs(argTables)
			metatable.donePairs = true
		end
		return translatenext, { t = metaArgs }
	end

	local function inext(t, i)
		-- Гэта выкарыстоўвае наш метаметад __index
		local v = t[i + 1]
		if v ~= nil then
			return i + 1, v
		end
	end

	metatable.__ipairs = function (t)
		-- Выклікаецца, калі ipairs выконваюцца ў табліцы args.
		return inext, t, 0
	end

	return args
end

return arguments