Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Documentation for this module may be created at Module:Params/doc

	---                                        ---
	---     LOCAL ENVIRONMENT                  ---
	---    ________________________________    ---
	---                                        ---


-- Special user-given keywords (functions and modifiers MUST avoid these names)
local mkeywords = {
--	['pattern'] = false,
	['plain'] = true,
	['or'] = 0
}


-- Set directives
local memoryslots = {
	i = 'itersep',
	p = 'pairsep',
	h = 'header',
	f = 'footer',
	n = 'ifngiven'
}


-- The private table of functions
local library = {}


-- Return a copy or a reference to a table
local function copy_or_ref_table(src, refonly)
	if refonly then return src end
	newtab = {}
	for key, val in pairs(src) do newtab[key] = val end
	return newtab
end


-- Prepare the context
local function context_init(frame, funcname, refpipe, refparams)
	local ctx = {}
	ctx.iterfunc = pairs
	ctx.pipe = copy_or_ref_table(frame.args, refpipe)
	ctx.frame = frame:getParent()
	ctx.params = copy_or_ref_table(ctx.frame.args, refparams)
	return funcname(ctx)
end


-- Move to the next action within the user-given list
local function context_iterate(ctx, n_forward)
	local nextfn
	if ctx.pipe[n_forward] ~= nil then
		nextfn = ctx.pipe[n_forward]:match'^%s*(.*%S)'
	end
	if nextfn == nil then
		error('No function name was given', 0)
	end
	if library[nextfn] == nil then
		error('The function "' .. nextfn .. '" does not exist', 0)
	end
	for idx = n_forward, 1, -1 do table.remove(ctx.pipe, idx) end
	return library[nextfn](ctx)
end


-- Concatenate the numerical keys from the table of parameters to the numerical
-- keys from the table of options; non-numerical keys from the table of options
-- will prevail over colliding non-numerical keys from the table of parameters
local function concat_params(ctx)
	local shift = table.maxn(ctx.pipe) 
	local newargs = {}
	if ctx.subset == 1 then
		-- We need only the sequence
		for key, val in ipairs(ctx.params) do
			newargs[key + shift] = val
		end
	else
		if ctx.subset == -1 then
			for key, val in ipairs(ctx.params) do
				ctx.params[key] = nil
			end
		end
		for key, val in pairs(ctx.params) do
			if type(key) == 'number' then
				newargs[key + shift] = val
			else
				newargs[key] = val
			end
		end
	end
	for key, val in pairs(ctx.pipe) do newargs[key] = val end
	return newargs
end


local function do_for_each_param(ctx, fn)
	local tbl = ctx.params
	if ctx.subset == 1 then
		for key, val in ipairs(tbl) do fn(key, val) end
		return
	end
	if ctx.subset == -1 then
		for key, val in ipairs(tbl) do tbl[key] = nil end
	end
	if ctx.dosort then
		local nums = {}
		local words = {}
		for key, val in pairs(tbl) do
			if type(key) == 'number' then
				nums[#nums + 1] = key
			else
				words[#words + 1] = key
			end

		end
		table.sort(nums)
		table.sort(words)
		for idx = 1, #nums do fn(nums[idx], tbl[nums[idx]]) end
		for idx = 1, #words do fn(words[idx], tbl[words[idx]]) end
		return
	end
	if ctx.subset ~= -1 then
		for key, val in ipairs(tbl) do
			fn(key, val)
			tbl[key] = nil
		end
	end
	for key, val in pairs(tbl) do fn(key, val) end
end


-- Parse the arguments of the `with_*_matching` class of modifiers
local function parse_match_args(opts, ptns, fname)
	local state = 0
	local cnt = 1
	local keyw
	for _, val in ipairs(opts) do
		if state == 0 then
			ptns[#ptns + 1] = { val, false }
			state = -1
		else
			keyw = val:match'^%s*(.*%S)'
			if keyw == nil or mkeywords[keyw] == nil then break
			else
				state = mkeywords[keyw]
				if state ~= 0 then ptns[#ptns][2] = state end
			end
		end
		cnt = cnt + 1
	end
	if state == 0 then error(fname .. ': No pattern was given', 0) end
	return cnt
end



	--[[ Piped modifiers ]]--
	--------------------------------


-- See iface.sequential()
library.sequential = function(ctx)
	if ctx.subset == -1 then error('The two directives "non-sequential" and "sequential" are in contradiction with each other', 0) end
	if ctx.dosort then error('The "all_sorted" directive is redundant when followed by "sequential"', 0) end
	ctx.iterfunc = ipairs
	ctx.subset = 1
	return context_iterate(ctx, 1)
end


-- See iface['non-sequential']()
library['non-sequential'] = function(ctx)
	if ctx.subset == 1 then error('The two directives "sequential" and "non-sequential" are in contradiction with each other', 0) end
	ctx.iterfunc = pairs
	ctx.subset = -1
	return context_iterate(ctx, 1)
end


-- See iface.all_sorted()
library.all_sorted = function(ctx)
	if ctx.subset == 1 then error('The "all_sorted" directive is redundant after "sequential"', 0) end
	ctx.dosort = true
	return context_iterate(ctx, 1)
end


-- See iface.setting()
library.setting = function(ctx)
	local opts = ctx.pipe
	local cmd
	if opts[1] ~= nil then
		cmd = opts[1]:gsub('%s+', ''):gsub('/+', '/'):match'^/*(.*[^/])'
	end
	if cmd == nil then error('setting: No directive was given', 0) end
	local sep = string.byte('/')
	local argc = 2
	local dest = {}
	local vname
	local chr
	for idx = 1, #cmd do
		chr = cmd:byte(idx)
		if chr == sep then
			for key, val in ipairs(dest) do
				ctx[val] = opts[argc]
				dest[key] = nil
			end
			argc = argc + 1
		else
			vname = memoryslots[string.char(chr)]
			if vname == nil then error('setting: Unknown slot "' ..
				string.char(chr) .. '"', 0) end
			table.insert(dest, vname)
		end
	end
	for key, val in ipairs(dest) do ctx[val] = opts[argc] end
	return context_iterate(ctx, argc + 1)
end


-- See iface.squeezing()
library.squeezing = function(ctx)
	local tbl = ctx.params
	local store = {}
	local indices = {}
	for key, val in pairs(tbl) do
		if type(key) == 'number' then
			indices[#indices + 1] = key
			store[key] = val
			tbl[key] = nil
		end
	end
	table.sort(indices)
	for idx = 1, #indices do tbl[idx] = store[indices[idx]] end
	return context_iterate(ctx, 1)
end


-- See iface.cutting()
library.cutting = function(ctx)
	local lcut = tonumber(ctx.pipe[1])
	if lcut == nil then error('cutting: Left cut must be a number', 0) end
	local rcut = tonumber(ctx.pipe[2])
	if rcut == nil then error('cutting: Right cut must be a number', 0) end
	local tbl = ctx.params
	local len = #tbl
	if lcut < 0 then lcut = len + lcut end
	if rcut < 0 then rcut = len + rcut end
	local tot = lcut + rcut
	if tot > 0 then
		local cache = {}
		if tot >= len then
			for key, val in ipairs(tbl) do tbl[key] = nil end
			tot = len
		else
			for idx = len - rcut + 1, len, 1 do tbl[idx] = nil end
			for idx = 1, lcut, 1 do tbl[idx] = nil end
		end
		for key, val in pairs(tbl) do
			if type(key) == 'number' and key > 0 then
				if key > len then
					cache[key - tot] = val
				else
					cache[key - lcut] = val
				end
				tbl[key] = nil
			end
		end
		for key, val in pairs(cache) do tbl[key] = val end
	end
	return context_iterate(ctx, 3)
end


-- See iface.with_name_matching()
library.with_name_matching = function(ctx)
	local tbl = ctx.params
	local patterns = {}
	local argc = parse_match_args(ctx.pipe, patterns, 'with_name_matching')
	local nomatch
	for key in pairs(tbl) do
		nomatch = true
		for _, ptn in ipairs(patterns) do
			if string.find(key, ptn[1], 1, ptn[2]) then
				nomatch = false
				break
			end
		end
		if nomatch then tbl[key] = nil end
	end
	return context_iterate(ctx, argc)
end


-- See iface.with_name_not_matching()
library.with_name_not_matching = function(ctx)
	local tbl = ctx.params
	local patterns = {}
	local argc = parse_match_args(ctx.pipe, patterns,
		'with_name_not_matching')
	local yesmatch
	for key in pairs(tbl) do
		yesmatch = true
		for _, ptn in ipairs(patterns) do
			if not string.find(key, ptn[1], 1, ptn[2]) then
				yesmatch = false
				break
			end
		end
		if yesmatch then tbl[key] = nil end
	end
	return context_iterate(ctx, argc)
end


-- See iface.with_value_matching()
library.with_value_matching = function(ctx)
	local tbl = ctx.params
	local patterns = {}
	local argc = parse_match_args(ctx.pipe, patterns,
		'with_value_matching')
	local nomatch
	for key, val in pairs(tbl) do
		nomatch = true
		for _, ptn in ipairs(patterns) do
			if string.find(val, ptn[1], 1, ptn[2]) then
				nomatch = false
				break
			end
		end
		if nomatch then tbl[key] = nil end
	end
	return context_iterate(ctx, argc)
end


-- See iface.with_value_not_matching()
library.with_value_not_matching = function(ctx)
	local tbl = ctx.params
	local patterns = {}
	local argc = parse_match_args(ctx.pipe, patterns,
		'with_value_not_matching')
	local yesmatch
	for key, val in pairs(tbl) do
		yesmatch = true
		for _, ptn in ipairs(patterns) do
			if not string.find(val, ptn[1], 1, ptn[2]) then
				yesmatch = false
				break
			end
		end
		if yesmatch then tbl[key] = nil end
	end
	return context_iterate(ctx, argc)
end


-- See iface.trimming_values()
library.trimming_values = function(ctx)
	local tbl = ctx.params
	for key, val in pairs(tbl) do tbl[key] = val:match'^%s*(.-)%s*$' end
	return context_iterate(ctx, 1)
end



	--[[ Piped functions ]]--
	------------------------------------


-- See iface.count()
library.count = function(ctx)
	local count = 0
	for _ in ctx.iterfunc(ctx.params) do count = count + 1 end
	if ctx.subset == -1 then count = count - #ctx.params end
	return count
end


-- See iface.concat_and_call()
library.concat_and_call = function(ctx)
	local opts = ctx.pipe
	local tname
	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
	if tname == nil then error('concat_and_call: No template name was provided', 0) end
	table.remove(opts, 1)
	return ctx.frame:expandTemplate{
		title = tname,
		args = concat_params(ctx)
	}
end


-- See iface.concat_and_invoke()
library.concat_and_invoke = function(ctx)
	local opts = ctx.pipe
	local mname
	local fname
	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
	if mname == nil then error('concat_and_invoke: No module name was provided', 0) end
	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
	if fname == nil then error('concat_and_invoke: No function name was provided', 0) end
	table.remove(opts, 2)
	table.remove(opts, 1)
	return require('Module:' .. mname)[fname](ctx.frame:newChild{
		title = 'Module:' .. fname,
		args = concat_params(ctx)
	})
end


-- See iface.value_of()
library.value_of = function(ctx)
	local opts = ctx.pipe
	local keystr
	if opts[1] ~= nil then keystr = opts[1]:match'^%s*(.*%S)' end
	if keystr == nil then error('value_of: No parameter name was provided', 0) end
	local keynum = tonumber(keystr)
	if (
		ctx.subset == -1 and keynum ~= nil and #ctx.params >= keynum
	) or (
		ctx.subset == 1 and (keynum == nil or #ctx.params < keynum)
	) then return (ctx.ifngiven or '') end
	local val = ctx.params[keynum or keystr]
	if val == nil then return (ctx.ifngiven or '') end
	return (ctx.header or '') .. val .. (ctx.footer or '')
end


-- See iface.list()
library.list = function(ctx)
	local pps = ctx.itersep or ''
	local kvs = ctx.pairsep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local ret = ''
	do_for_each_param(
		ctx,
		function(key, val)
			ret = ret .. sep .. key .. kvs .. val
			sep = pps
			foo = las
		end
	)
	return ret .. foo
end


-- See iface.list_values()
library.list_values = function(ctx)
	local pps = ctx.itersep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local ret = ''
	do_for_each_param(
		ctx,
		function(key, val)
			ret = ret .. sep .. val
			sep = pps
			foo = las
		end
	)
	return ret .. foo
end


-- See iface.for_each()
library.for_each = function(ctx)
	local pps = ctx.itersep or ''
	local las = ctx.footer or ''
	local sep = ctx.header or ''
	local foo = ctx.ifngiven or ''
	local txt = ctx.pipe[1] or ''
	local ret = ''
	do_for_each_param(
		ctx,
		function(key, val)
			ret = ret .. sep .. string.gsub(
				string.gsub(txt, '%$#', key),
				'%$@',
				val
			)
			sep = pps
			foo = las
		end
	)
	return ret .. foo
end


-- See iface.call_for_each()
library.call_for_each = function(ctx)

	local opts = ctx.pipe
	local tname

	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
	if tname == nil then error('call_for_each: No template name was provided', 0) end

	local ccs = ctx.itersep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local model = { title = tname, args = opts }
	local ret = ''

	table.insert(opts, 1, true)

	do_for_each_param(
		ctx,
		function(key, val)
			opts[1] = key
			opts[2] = val
			ret = ret .. sep .. ctx.frame:expandTemplate(model)
			sep = ccs
			foo = las
		end
	)

	return ret .. foo

end


-- See iface.invoke_for_each()
library.invoke_for_each = function(ctx)

	local opts = ctx.pipe
	local mname
	local fname

	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
	if mname == nil then error('invoke_for_each: No module name was provided', 0) end
	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
	if fname == nil then error('invoke_for_each: No function name was provided', 0) end

	local ccs = ctx.itersep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local model = { title = 'Module:' .. mname, args = opts }
	local mfunc = require(model.title)[fname]
	local ret = ''

	do_for_each_param(
		ctx,
		function(key, val)
			opts[1] = key
			opts[2] = val
			ret = ret .. sep .. mfunc(ctx.frame:newChild(model))
			sep = ccs
			foo = las
		end
	)

	return ret .. foo

end


-- See iface.magic_for_each()
library.magic_for_each = function(ctx)

	local opts = ctx.pipe
	local magic

	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
	if magic == nil then error('magic_for_each: No parser function was provided', 0) end

	local ccs = ctx.itersep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local ret = ''

	table.insert(opts, 1, true)

	do_for_each_param(
		ctx,
		function(key, val)
			opts[1] = key
			opts[2] = val
			ret = ret .. sep .. ctx.frame:callParserFunction(magic, opts)
			sep = ccs
			foo = las
		end
	)

	return ret .. foo

end


-- See iface.call_for_each_value()
library.call_for_each_value = function(ctx)

	local opts = ctx.pipe
	local tname

	if opts[1] ~= nil then tname = opts[1]:match'^%s*(.*%S)' end
	if tname == nil then error('call_for_each_value: No template name was provided', 0) end

	local ccs = ctx.itersep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local model = { title = tname, args = opts }
	local ret = ''

	do_for_each_param(
		ctx,
		function(key, val)
			opts[1] = val
			ret = ret .. sep .. ctx.frame:expandTemplate(model)
			sep = ccs
			foo = las
		end
	)

	return ret .. foo

end


-- See iface.invoke_for_each_value()
library.invoke_for_each_value = function(ctx)

	local opts = ctx.pipe
	local mname
	local fname

	if opts[1] ~= nil then mname = opts[1]:match'^%s*(.*%S)' end
	if mname == nil then error('invoke_for_each_value: No module name was provided', 0) end
	if opts[2] ~= nil then fname = opts[2]:match'^%s*(.*%S)' end
	if fname == nil then error('invoke_for_each_value: No function name was provided', 0) end

	local ccs = ctx.itersep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local model = { title = 'Module:' .. mname, args = opts }
	local mfunc = require(model.title)[fname]
	local ret = ''

	table.remove(opts, 1)

	do_for_each_param(
		ctx,
		function(key, val)
			opts[1] = val
			ret = ret .. sep .. mfunc(ctx.frame:newChild(model))
			sep = ccs
			foo = las
		end
	)

	return ret .. foo

end


-- See iface.magic_for_each_value()
library.magic_for_each_value = function(ctx)

	local opts = ctx.pipe
	local magic

	if opts[1] ~= nil then magic = opts[1]:match'^%s*(.*%S)' end
	if magic == nil then error('magic_for_each_value: No parser function was provided', 0) end

	local ccs = ctx.itersep or ''
	local sep = ctx.header or ''
	local las = ctx.footer or ''
	local foo = ctx.ifngiven or ''
	local ret = ''

	do_for_each_param(
		ctx,
		function(key, val)
			opts[1] = val
			ret = ret .. sep .. ctx.frame:callParserFunction(magic,
				opts)
			sep = ccs
			foo = las
		end
	)

	return ret .. foo

end



	---                                        ---
	---     PUBLIC ENVIRONMENT                 ---
	---    ________________________________    ---
	---                                        ---


-- The public table of functions
local iface = {}



	--[[ Non-piped modifiers ]]--
	------------------------------------


-- Syntax:  #invoke:params|sequential|function name
iface.sequential = function(frame)
	return context_init(frame, library.sequential, false, false)
end


-- Syntax:  #invoke:params|non-sequential|function name
iface['non-sequential'] = function(frame)
	return context_init(frame, library['non-sequential'], false, false)
end


-- Syntax:  #invoke:params|sort|function name
iface.all_sorted = function(frame)
	return context_init(frame, library.all_sorted, false, false)
end


-- Syntax:  #invoke:params|setting|directives|...|function name
iface.setting = function(frame)
	return context_init(frame, library.setting, false, false)
end


-- Syntax:  #invoke:params|squeezing|function name
iface.squeezing = function(frame)
	return context_init(frame, library.squeezing, false, false)
end


-- Syntax:  #invoke:params|cutting|left cut|right cut|function name
iface.cutting = function(frame)
	return context_init(frame, library.cutting, false, false)
end


-- Syntax:  #invoke:params|with_name_matching|pattern 1|[plain flag 1]|[or]
--            |[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag
--            N]|function name
iface.with_name_matching = function(frame)
	return context_init(frame, library.with_name_matching, false, false)
end


-- Syntax:  #invoke:params|with_name_not_matching|pattern 1|[plain flag 1]
--            |[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain
--            flag N]|function name
iface.with_name_not_matching = function(frame)
	return context_init(frame, library.with_name_not_matching, false,
		false)
end


-- Syntax:  #invoke:params|with_value_matching|pattern 1|[plain flag 1]|[or]
--            |[pattern 2]|[plain flag 2]|[or]|[...]|[pattern N]|[plain flag
--            N]|function name
iface.with_value_matching = function(frame)
	return context_init(frame, library.with_value_matching, false, false)
end


-- Syntax:  #invoke:params|with_value_not_matching|pattern 1|[plain flag 1]
--            |[and]|[pattern 2]|[plain flag 2]|[and]|[...]|[pattern N]|[plain
--            flag N]|function name
iface.with_value_not_matching = function(frame)
	return context_init(frame, library.with_value_not_matching, false,
		false)
end


-- Syntax:  #invoke:params|trimming_values|function name
iface.trimming_values = function(frame)
	return context_init(frame, library.trimming_values, false, false)
end



	--[[ Non-piped functions ]]--
	----------------------------------------


-- Syntax:  #invoke:params|count
iface.count = function(frame)
	return context_init(frame, library.count, true, true)
end


-- Syntax:  #invoke:args|concat_and_call|template name|[prepend 1]|[prepend 2]
--            |[...]|[item n]|[named item 1=value 1]|[...]|[named item n=value
--            n]|[...]
iface.concat_and_call = function(frame)
	return context_init(frame, library.concat_and_call, false, true)
end


-- Syntax:  #invoke:args|concat_and_invoke|module name|function name|[prepend
--            1]|[prepend 2]|[...]|[item n]|[named item 1=value 1]|[...]|[named
--            item n=value n]|[...]
iface.concat_and_invoke = function(frame)
	return context_init(frame, library.concat_and_invoke, false, true)
end


-- Syntax:  #invoke:params|value_of|parameter name
iface.value_of = function(frame)
	return context_init(frame, library.value_of, true, true)
end


-- Syntax:  #invoke:params|list
iface.list = function(frame)
	return context_init(frame, library.list, true, false)
end


-- Syntax:  #invoke:params|list_values
iface.list_values = function(frame)
	return context_init(frame, library.list_values, true, false)
end


-- Syntax:  #invoke:params|for_each|wikitext
iface.for_each = function(frame)
	return context_init(frame, library.for_each, true, false)
end


-- Syntax:  #invoke:params|call_for_each|template name|[append 1]|[append 2]
--            |[...]|[append n]|[named param 1=value 1]|[...]|[named param
--            n=value n]|[...]
iface.call_for_each = function(frame)
	return context_init(frame, library.call_for_each, false, false)
end


-- Syntax:  #invoke:params|invoke_for_each|module name|module function|[append
--            1]|[append 2]|[...]|[append n]|[named param 1=value 1]|[...]
--            |[named param n=value n]|[...]
iface.invoke_for_each = function(frame)
	return context_init(frame, library.invoke_for_each, false, false)
end


-- Syntax:  #invoke:params|magic_for_each|parser function|[append 1]|[append 2]
--            |[...]|[append n]|[named param 1=value 1]|[...]|[named param
--            n=value n]|[...]
iface.magic_for_each = function(frame)
	return context_init(frame, library.magic_for_each, false, false)
end


-- Syntax:  #invoke:params|call_for_each_value|template name|[append 1]|[append
--            2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param
--            n=value n]|[...]
iface.call_for_each_value = function(frame)
	return context_init(frame, library.call_for_each_value, false, false)
end


-- Syntax:  #invoke:params|invoke_for_each_value|module name|[append 1]|[append
--            2]|[...]|[append n]|[named param 1=value 1]|[...]|[named param
--            n=value n]|[...]
iface.invoke_for_each_value = function(frame)
	return context_init(frame, library.invoke_for_each_value, false, false)
end


-- Syntax:  #invoke:params|magic_for_each_value|parser function|[append 1]
--            |[append 2]|[...]|[append n]|[named param 1=value 1]|[...]|[named
--            param n=value n]|[...]
iface.magic_for_each_value = function(frame)
	return context_init(frame, library.magic_for_each_value, false, false)
end


return iface