Модуль:SummaryII/args

Материал из свободной русской энциклопедии «Традиция»
Перейти к навигации Перейти к поиску

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

-- Dependencies:
local lc, sub, gsub, find = mw.ustring.lower, mw.ustring.sub, mw.ustring.gsub, mw.ustring.find
local trim, split = mw.text.trim, mw.text.split
-- Service functions:
local function strip (name)
	return gsub (lc (tostring(name)), '[%s%p]', '')
end	-- local function strip (name)

local function subst (str, tbl)
	local ret = str
	for key, val in pairs (tbl) do
		ret = gsub (ret, '\\' .. key, tostring (val))
	end
	return ret
end	-- local function subst (str, tbl)

local function merge (...)
	local res = {}
	for _, tbl in ipairs {...} do
		setmetatable (res, getmetatable (tbl))
		for key, val in pairs (tbl) do
			if type (key) == 'number' and res [key] ~= nil then
				res [#res + 1] = val
			else
				res [key] = val
			end
		end
	end	-- for _, tbl in ipairs {...}
	return res
end	-- local function merge (...)
				
local function deep_table ()
	return setmetatable ({}, {
		__newindex = function (tbl, index, val)
			local first = strip (table.remove (index, 1))
			first = tonumber (first) or first
			if #index == 0 then
				rawset (tbl, first, tonumber (val) or val)
			else
				-- recursion:
				rawset (tbl, first, tbl [first] or deep_table ())
				tbl [first] [index] = val
			end	-- if #index == 0
		end	-- __newindex = function (tbl, index, val)
	})	-- return setmetatable ({}, {...})
end	-- local function deep_table ()

-- Get template or module arguments:
local function get_args (frame)
	-- На случай вызова из шаблона и из модуля:
	local args = frame:getParent () and mw.clone (frame:getParent ().args) or {}
	-- Overrides:
	for key, val in pairs (frame.args) do
		args [key] = val
	end
	return args
end		-- local function get_args (frame)

-- Interpret short syntax (key = val) of parsing rule:
local function interpret (key, val)
	local key_filter, key_target, val_filter, val_target
	if		type (key) == 'number' then
		-- key filter:
		key_filter = val
	elseif	type (key) ~= 'table' then
		-- key target = ...:
		key_target = key
		if type (val) ~= 'table' then
			-- key target = key filter:
			key_filter = val
		else
			-- key target = {value target = value filter}:
			key_filter	= key
			val_target	= next (val)
			val_filter	= val [val_target]
		end
	elseif	type (key) == 'table' then
		-- {key target = key filter} = ...:
		key_target = next (key)
		key_filter = key [key_target]
		if type (val) ~= 'table' then
			-- {key target = key filter} = value filter:
			val_filter = val
		else
			-- {key target = key filter} = {value target = value filter}:
			val_target = next (val)
			val_filter = val [val_target]
		end
	end
	return key_filter, key_target, val_filter, val_target
end	-- local function interpret (rey, val)

local function test_rule (str, filter, full)
	local success, captures = false, {}
	if not filter then
		-- no filter:
		success, captures = true, {[full or 0] = str}
	elseif type (filter) == 'string' then
		-- plain filter:
		success = str == filter
		captures = {[full or 0] = str}
	elseif sub (tostring (filter), 1, 14) == 'rex_pcre_regex' then
		-- RCRE filter:
		local start, finish
		start, finish, captures = filter:tfind (str)
		success = start ~= nil
		if success then
			captures [full or 0] = sub (str, start, finish - start + 1)
		end
	elseif	lpeg.type (filter) == 'pattern' then
		-- LPEG filter:
		captures = {filter:match (tostring (str))}
		success = next (captures) ~= nil
		captures [full or 0] = str
	else
		-- unrecognised filter.
	end
	return success, captures
end	-- local function test_rule (str, filter, full)

--[[
	This function rearranges args according to rules and returns:
--]]
local function parse (args, rules)
	local res = deep_table ()
	local stripped = {}
	for rule1, rule2 in pairs (rules) do
		local key_filter, key_target, val_filter, val_target = interpret (rule1, rule2)
		for key, val in pairs (args) do
			-- Cache key brought to lower case and stripped of spaces and punctuation:
			stripped [key] = stripped [key] or strip (key)
			local stripped_key = stripped [key]
			-- Parse key:
			local key_fits, key_captures = test_rule (stripped_key, key_filter, '__key')
			if key_fits then
				-- Parse value:
				local val_fits, val_captures = test_rule (val, val_filter)
				if val_fits then
					-- if both tables are not empty:
					local captures = merge (key_captures, val_captures)
					res [split (subst (key_target or stripped_key, captures), '/')]
						= subst (val_target or val, captures)
				end
			end
		end	-- for key, val in pairs (args)
	end	-- for rule1, rule2 in pairs (rules)
	return res
end	-- local function parse (args, rules)

return {
	get		= get_args
  , parse	= parse
  , test	= function (no)
  		local regex	= rex_pcre.new
  		local re	= require 'Module:Re'.compile
  		local tests = {
  			{
  				rules = {
		  			total											= {['\\0'] = regex '^\\d+$'}
		  		  , [{['no / \\1']		= regex 'no(\\d+)'}]		= regex '\\d+'
		  		  , [{['part / \\no']	= regex 'part(?<no>\\d+)'}] = regex '\\d+'
				  , ['free / \\1']									= regex 'free(\\d+)'
				  , title											= regex '^название$'
		  		}	-- rules
			  , args = {
		  			total		= 120
		  		  , No_1		= 10
		  		  , no2			= 20
		  		  , no3			= 'Сапоги всмятку'
		  		  , free2		= 'Free 2'
		  		  , free_4		= 'Free 4'	  
		  		  , ['Free 5']	= 'Free 5'
		  		  , ['part 1']	= 1
		  		  , ['Название']= 'А вот и название'
		  		}
		  	}, {
				rules = {
					['part/\\1']= regex '^раздел(\\d+)$'
				  , nocontent	= regex '^безсодержания$'
				  , current		= regex '^раздел$'
				  , currentno	= regex '^№раздела$'
				} -- rules
			  , args = {
					['раздел0']			= 'Введение'
				  ,	['раздел 1']		= 'Глава I'
				  ,	['раздел_2']		= 'Глава II'
				  ,	['Раздел 3']		= 'Заключение'
				  , ['без содержания']	= 'да'
				  ,	['Раздел']			= 'Глава II'
				  ,	['№ раздела']		= 2
				}
			}
		}	-- local tests
		local t = parse (tests [no].args, tests [no].rules)
  		return require 'Module:Test'.serialise (t)
  	end	-- test	= function (no)
}