Модуль:SummaryII/params

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

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

--[[
	Dependencies:
--]]
local dep = require 'Module:SummaryII/dependencies'
local lc, sub, gsub, find = dep.lc, dep.sub, dep.gsub, dep.find
local trim, split = dep.trim, dep.split
local lpeg, regex, re = dep.lpeg, dep.regex, dep.re

-- 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 sort = table.sort				
local function ordered_pairs (tbl)
	local keys = {}
	for key, _ in pairs (tbl) do
		keys [#keys + 1] = tonumber (key) or key
	end
	sort (keys)
	local current = 0
	return function (tbl, key)
		current = current + 1
		local key = keys [current]
		if key then
			return key, tbl [key]
		end
	end, tbl, nil
end	-- local function ordered_pairs (tbl)
	
local P,		V,		C,		Cc,			Cf,			Cg,			Cnil,			s
	= lpeg.P,	lpeg.V,	lpeg.C,	lpeg.Cc,	lpeg.Cf,	lpeg.Cg,	lpeg.Cc (nil),	lpeg.locale ().space
local S, any = s ^ 0, P(1)
local	quote,	quote2,	at,		comma,	equals,	slash,	tick,	dot,	amp,	open,	close,	escape
	=	"'",	'"',	'@',	',',	 '=',	'/',	'`',	'.',	'&',	'(',	')',	'\\'
local flags = lpeg.S'g' ^ 0
	
local function sans (...)
	local forbidden = P (false)
	for _, char in ipairs {...} do
		forbidden = forbidden + char
	end
	return (any - forbidden + escape * forbidden) ^ 1
end	-- local function sans (...)

-- 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)

local function grammar (inject, terminate)

	local func_name = P (false)
	if inject and type (inject) == 'table' then
		for key, func in pairs (inject) do
			func_name = func_name + key
		end
	end
	
	local function trivial (tbl)
		return tbl
	end	-- local function trivial (tbl)
	
	local function rekey (tbl, check)
		local result = {}
		for _, line in pairs (tbl) do
	  		if type (line) == 'table' then
  				for key, val in pairs (line) do
  					if check (key, val) then
  						result [tonumber (val) or val] = line
  						break
  					end
  				end
  			end
  		end
  		return result
  	end	-- local function rekey (tbl, check)
  	
  	local function enter (tbl, check)
		local search = tbl [next (tbl)] [0]
	  	for key, val in pairs (search) do
	  		if check (key, val) then
	  			return search [key]
	  		end
	  	end
	end -- 	local function enter (tbl, check)
	  
	local function filter (tbl, check)
		local res = {}
	  	for key, val in pairs (type (tbl) == 'table' and tbl or {tbl}) do
	  		local check_key = strip (key)
	  		local check_val = type (val) == 'table' and val [0] or val
			local success, captures = check (check_key, check_val)
	  		if success then
				captures [0] = check_val
				res [key] = merge (type (val) == 'table' and val or {}, captures)
			end
		end
		return res
	end	-- local function filter (tbl, check)

	local function make_regex (pattern)
		return function (str)
			local start, finish, captures = pattern:tfind (tostring (str))
			return start ~= nil, captures
	  	end	-- return function (str)
	end	-- local function make_regex (pattern)
	
	local function make_re (pattern)
		return function (str)
	  		local captures = {pattern:match (tostring (str))}
			return next (captures) ~= nil, captures
	  	end	-- return function (str)
	end	-- local function make_re (pattern)
	
	local function make_plain (pattern)
	  	local filter = trim (pattern)
		return function (str)
	  		return filter == tostring (str), {}
	  	end	-- return function (str)
	end	-- local function make_plain (pattern)
	
	return P{'choice'
		, choice	= V'chain' * (S * comma * S * V'chain') ^ 0 / function (...)
	   		local alternatives = {...}
	  		return function (tbl)
	  			for _, alternative in ipairs (alternatives) do
	  				local result = alternative (tbl)
	  				if result and not (type (result) == 'table' and not next (result)) then
	  					-- not nil and not an empty table.
	  					if type (result) == 'table' then
	  						local captures = result [next (result)]
	  						return captures [0], captures
	  					else
	  						return result
	  					end
	  				end
	  			end
	  		end	-- return function (tbl)
		end	-- choice = V'chain' * (S * comma * S * V'chain) ^ -1 / function (...)
		-- Unfortunately, no left recursion in LPEG; so, a fold capture:
		, chain		= Cf (Cc (trivial) * Cg (V'combine' * V'check') ^ 1, function (filter, combiner, check)
	  		return function (tbl)
	  			return combiner (filter (tbl), check)
	  		end
	  	end)
	  , combine		= V'parentheses' + V'rekey' + V'enter' + V'filter'
	  , parentheses	= S * open * S * V'choice' * S * close * S
	  , rekey		= S * at * S * Cc (rekey)
	  , enter		= S * dot * S * Cc (enter)
	  , filter		= Cc (filter)
	  , check		= V'check_value' + V'check_key'
	  , check_value	= S * equals * S * V'engine' / function (engine)
	  		return function (key, val)
	  			return engine (type (val) == 'table' and val [0] or val)
	  		end
	  	end	-- check_value = equals_sp * V'engine' / function (engine)
	  , check_key	= V'engine' / function (engine)
	  		return function (key, val)
	  			return engine (key)
	  		end
	  	end	-- check_key = V'engine' / function (engine)
	  , engine		= V'inject' + V'regex' + V're' + V'plain'
	  , inject		= S * amp * S * C(func_name) / function (func_name)
	  		return function (str)
	  			return inject [func_name]
	  		end
	  	end	-- inject = S * amp * S * C(func_name) / function (func_name)
	  , regex		= (S * slash * C(sans (slash)) / regex * slash * S) / make_regex
	  , re			= (S * tick * C(sans (tick)) / re * tick * S) / make_re
	  , plain		= quote * C(sans (quote)) / make_plain * quote
	  				+ quote2 * C(sans (quote2)) / make_plain * quote2
	  				+ C(sans (equals, comma, at, dot, slash, tick, open, close,
	  						  unpack (terminate or {}))) / make_plain
	}	-- return P{'choice', ...}
end	-- local function grammar (inject, terminate)

local path_grammar = grammar ()
local function rule (code)
	return path_grammar:match (code)
end	-- local function rule (code)

local function parse (tbl, rules)
	local res = {}
	for target, code in pairs (rules) do
		res [target] = rule (code) (tbl) or 'NIL'
	end
	return res
end	-- local function parse (tbl, rules)

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