Module:Arguments

来自滚动的天空Wiki
文档图示 模块文档[查看] [编辑] [查看历史] [清除缓存]

此模块提供了对通过{{#invoke:}}(以下简称#invoke)传递参数的简单处理。这是元模块(meta-module),只能被其他模块使用,而不应被#invoke直接调用。其特性如下:

  • 对参数的简易修整,移除空白参数。
  • 参数可以在当前框架或父框架中同时传递。(具体见下)
  • 参数可以直接通过其他Lua模块或调试控制台传递。
  • 可自定义更多特性。

基本用法[编辑源代码]

首先,您需要通过require函数加载这个模块。这个模块包含了一个名为getArgs的函数。

local getArgs = require('Module:Arguments').getArgs

最简单的方法是在使用getArgs函数。变量args是包含#invoke参数的表(table)。(详见下文。)

local getArgs = require('Module:Arguments').getArgs
local p = {}

function p.main(frame)
	local args = getArgs(frame)
	-- 主要的模块放此处。
end

return p

最佳实践[编辑源代码]

最佳的做法是,先用专门的函数来处理来自#invoke的参数。这样,其他Lua模块直接调用该模块时,就无需再弄一个frame对象,从而提升性能,减小开销。

local getArgs = require('Module:Arguments').getArgs
local p = {}

function p.main(frame)
	local args = getArgs(frame) -- 从#invoke中获得的参数
	return p._main(args)
end

function p._main(args)
	-- 主要模块放此处。
end

return p

多个函数[编辑源代码]

如果你需要多个函数使用这些参数,而且你希望这些函数可用于#invoke,你可以使用包装函数(wrapper function)。

local getArgs = require('Module:Arguments').getArgs

local p = {}

local function makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame)
		return p[funcName](args)
	end
end

p.func1 = makeInvokeFunc('_func1')

function p._func1(args)
	-- 第一个函数的代码。
end

p.func2 = makeInvokeFunc('_func2')

function p._func2(args)
	-- 第二个函数的代码。
end

return p

选项[编辑源代码]

你可以使用如下面这段代码所示的选项。这些选项会在下文中介绍。

local args = getArgs(frame, {
	trim = false,
	removeBlanks = false,
	valueFunc = function (key, value)
		-- 用于处理一个参数的函数的代码。
	end,
	frameOnly = true,
	parentOnly = true,
	parentFirst = true,
	wrappers = {
		'Template:一个包装模板',
		'Template:另一个模板'
	},
	readOnly = true,
	noOverwrite = true
})

修整参数和移除空白的参数[编辑源代码]

将模板转换为Lua的新手易在空白参数上犯错。在模板语法中,空白字符串和仅包含空白字符(whitespace,空格、换行等)的字符串被视为假(false)。然而,在Lua,空白字符串和只包含空白字符的字符串则会被视为真(true)。这就是说,如果你在写Lua模块时,不注意这些参数,你可能会把本想视为假的东西视为真。为了避免这种情况,这个模块默认会移除所有的空白参数。

类似地,空白字符在处理位置参数(positional arguments)时会发生问题。虽然来自#invoke的具名参数(named arguments)中的多余空白字符会被修整(trim),但是对一些位置参数仍然保留。大多数时候,多余的空白字符是不需要的,所以这个模块默认剔除这些空白字符。

然而,有时输入时又需要使用这些空白字符,或者需要保留空白参数。把某些模板准确地转化为模块时,可能有必要这么做。如果你需要这样,你可以将trimremoveBlanks参数设为false

local args = getArgs(frame, {
	trim = false,
	removeBlanks = false
})

对参数进行自定义格式化[编辑源代码]

有时,你需要移除某些空白参数,但是还有些空白参数又不想移除,或者,你需要将所有位置参数转化为小写字母。你可以使用valueFunc选项。这个参数的值必须是一个接收两个参数keyvalue并且只返回一个值的函数,这个值是你在args表中索引名为key的域时得到的值。

例1:这个函数不会动第一个参数的空白字符,但是其他参数的空白字符会剔除并移除其他所有空白参数。

local args = getArgs(frame, {
	valueFunc = function (key, value)
		if key == 1 then
			return value
		elseif value then
			value = mw.text.trim(value)
			if value ~= '' then
				return value
			end
		end
		return nil
	end
})

例2:这个函数移除空白参数并将所有参数转化为小写字母,但是不会剔除位置参数的空白字符。

local args = getArgs(frame, {
	valueFunc = function (key, value)
		if not value then
			return nil
		end
		value = mw.ustring.lower(value)
		if mw.ustring.find(value, '%S') then
			return value
		end
		return nil
	end
})

注:如果传入了既不是字符串又不是空值(nil)的值,上面这个函数会失败。当你在你的模块的主函数使用getArgs函数,而且那个函数被另一个Lua模块调用时,就可能出现此情况。这种情况下,你需要检查你输入的内容的类型(type)。如果你使用一个专门用于来自#invoke的参数的函数时,不会有这个问题,你如你有p.mainp._main函数,或者类似。

改进后的例1:

local args = getArgs(frame, {
	valueFunc = function (key, value)
		if key == 1 then
			return value
		elseif type(value) == 'string' then
			value = mw.text.trim(value)
			if value ~= '' then
				return value
			else
				return nil
			end
		else
			return value
		end
	end
})

改进后的例2:

local args = getArgs(frame, {
	valueFunc = function (key, value)
		if type(value) == 'string' then
			value = mw.ustring.lower(value)
			if mw.ustring.find(value, '%S') then
				return value
			else
				return nil
			end
		else
			return value
		end
	end
})

而且,请注意,每次从args表中请求参数时,都会调用valueFunc函数,所以请留意性能,确保不要加入低效的代码。

框架与父框架[编辑源代码]

args表中的参数可以从当前框架或父框架同时传递。这句话有点难懂,可以看下面的例子。假设我们有个称为模块:ExampleArgs的模块,这个模块输出(print)前两个传入的位置参数。

模块:ExampleArgs的代码

local getArgs = require('Module:Arguments').getArgs
local p = {}

function p.main(frame)
	local args = getArgs(frame)
	return p._main(args)
end

function p._main(args)
	local first = args[1] or ''
	local second = args[2] or ''
	return first .. ' ' .. second
end

return p


然后,模块:ExampleArgs模板:ExampleArgs调用,模板:ExampleArgs内容如下:{{#invoke:ExampleArgs|main|firstInvokeArg}}。它会输出内容firstInvokeArg。

现在,如果我们调用模板:ExampleArgs,其结果如下表所示:

代码 结果
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstInvokeArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstInvokeArg secondTemplateArg

有三个选项可以用来改变行为:frameOnlyparentOnlyparentFirst。如果设置frameOnly,那么只有从当前框架传入的参数可以被接受;如果设置 parentOnly,那么只有从父框架传入的参数会被接受;如果你设置parentFirst,那么当前框架和父框架的参数都会接受,但是父框架优先于当前框架。以下是对于模板:ExampleArgs的结果:

设为frameOnly时
代码 结果
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstInvokeArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstInvokeArg
设为parentOnly时
代码 结果
{{ExampleArgs}}
{{ExampleArgs|firstTemplateArg}} firstTemplateArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstTemplateArg secondTemplateArg
设为parentFirst时
代码 结果
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstTemplateArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstTemplateArg secondTemplateArg

注意:

  1. 如果你同时设置了frameOnlyparentOnly两个选项,模块将不会从#invoke获取任何参数。这显然不是你需要的。
  2. 有时,父框架可能无效,比如getArgs是从父框架传入的,而不是当前框架。这种情况下,只有框架参数会被使用(除非设置了parentOnly,那种情况下不会使用任何参数),而且parentFirstframeOnly选项都会没有效果。

包装[编辑源代码]

包装(wrapper)选项用于指定一部分模板作为包装模板(wrapper templates),也就是说,要调用模块的模板。如果模块检测到是被包装模板调用的,则只会检查父框架中的参数;否则,只检查传递到getArgs的框架的参数。这允许模块要么被#invoke调用,要么通过包装模板调用,而不会由于为每次参数寻找(argument lookup)同时检查框架和父框架而损失性能。

比如,维基百科的w:Template:Side box的内容(除了<noinclude>...</noinclude>标签内的)为{{#invoke:Side box|main}}。检查直接传递到模板的#invoke语句的参数是没有道理的,因为这里没有指定参数。我们可以通过parentOnly选项避免检查传递到#invoke的参数,但如果这样做,#invoke也不会从其他页面起作用。如果是这样,代码{{#invoke:Side box|main|text=一些文本}}中的text=一些文本会直接忽略,无论是从哪个页面使用的。使用wrappers选项以指定“Template:Side box”为包装,我们可以使得{{#invoke:Side box|main|text=一些文本}}能够从大多数页面使用,而不需要检查Template:Side box页面自身的参数。

容器可以指定为字符串,或字符串的数组。

local args = getArgs(frame, {
	wrappers = 'Template:Wrapper template'
})


local args = getArgs(frame, {
	wrappers = {
		'Template:Wrapper 1',
		'Template:Wrapper 2',
		-- 可以在此处添加多个包装模板。
	}
})

注意:

  1. 模块会自动检测是否是从包装模板的/sandbox子页面调用的,所以不需要清楚地指定沙盒页面。
  2. wrappers选项有效改变frameOnlyparentOnly的默认的选项。如果,比如,设置了wrappers时清楚地将parentOnly设为false,通过包装模板调用会导致同时加载框架和父框架的参数,尽管非经由包装模板的调用会导致只加载框架参数。
  3. 如果设置了wrappers但是没有可用的父框架,模块总是会从传递给getArgs的框架中得到参数。

写入参数表[编辑源代码]

有时给参数表写入新值会很有用。这可以通过此模块的默认设置实现。(然而,记住最好的代码风格是,将需要的参数表中的参数复制到一个新的表中。)

args.foo = '一些值'

可以带有readOnlynoOverwrite选项修改此行为。如果设置了readOnly,则完全不可能将任何值写到参数表中。如果设置了noOverwrite,则可以将新值添加到此表,但是如果需要重写从#invoke传递的任何参数则不可能添加值。

ref标签[编辑源代码]

模块使用元表以从#invoke中获取参数。这允许不使用pairs()函数就获取框架参数和父框架参数。如果你需要将<ref>...</ref>标签作为输入时,这会很有用。

<ref>...</ref>标签是从Lua中获取的,因此会被MediaWiki软件处理,引用会在文章底部的参考文献列表中显示。如果模块继续从输出中省略索引标签,则会产生一个假引用 —— 在参考文献列表中显示,但是没有与之链接的数字。模块如果使用pairs()来检测是否从框架或父框架中使用参数,就会出现此问题,因为这些模块会自动处理每一个可用变量。

此模块允许既获取框架又获取父框架而仅在需要时获取这些参数,从而解决此问题。然而,模块其他位置使用pairs(args)时,仍会出现此问题。

已知限制[编辑源代码]

元表(metatable)的使用也有其缺点。大多数正常Lua表工具都不会对args表正常工作,包括#操作符号、next()函数和表库(table library)中的函数。如果这对你的模块重要,你需要使用你自己的用来处理参数的函数,而不是这个模块。

上述文档内容嵌入自Module:Arguments/doc编辑 | 历史
编者可以在本模块的沙盒创建 | 镜像和测试样例创建页面进行实验。
请将模块自身所属的分类添加在文档中。本模块的子页面
-- 本内容来自维基百科
-- This module provides easy processing of arguments passed to Scribunto from
-- #invoke. It is intended for use by other Lua modules, and should not be
-- called from #invoke directly.

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

local arguments = {}

-- Generate four different tidyVal functions, so that we don't have to check the
-- options every time we call it.

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

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

	--[[
	-- Get the argument tables. If we were passed a valid frame object, get the
	-- frame arguments (fargs) and the parent frame arguments (pargs), depending
	-- on the options set and on the parent frame's availability. If we weren't
	-- passed a valid frame object, we are being called from another Lua module
	-- or from the debug console, so assume that we were passed a table of args
	-- directly, and assign it to a new variable (luaArgs).
	--]]
	local fargs, pargs, luaArgs
	if type(frame.args) == 'table' and type(frame.getParent) == 'function' then
		if options.wrappers then
			--[[
			-- The wrappers option makes Module:Arguments look up arguments in
			-- either the frame argument table or the parent argument table, but
			-- not both. This means that users can use either the #invoke syntax
			-- or a wrapper template without the loss of performance associated
			-- with looking arguments up in both the frame and the parent frame.
			-- Module:Arguments will look up arguments in the parent frame
			-- if it finds the parent frame's title in options.wrapper;
			-- otherwise it will look up arguments in the frame object passed
			-- to getArgs.
			--]]
			local parent = frame:getParent()
			if not parent then
				fargs = frame.args
			else
				local title = parent:getTitle():gsub('/sandbox$', '')
				local found = false
				if type(options.wrappers) == 'table' then
					for _,v in pairs(options.wrappers) do
						if v == title then
							found = true
							break
						end
					end
				elseif options.wrappers==title or options.wrapper==title then
					found = true
				end
				
				-- We test for false specifically here so that nil (the default) acts like true.
				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 isn't set, so check the other options.
			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
	
	-- Set the order of precedence of the argument tables. If the variables are
	-- nil, nothing will be added to the table, which is how we avoid clashes
	-- between the frame/parent args and the Lua args.	
	local argTables = {fargs}
	argTables[#argTables + 1] = pargs
	argTables[#argTables + 1] = luaArgs

	--[[
	-- Generate the tidyVal function. If it has been specified by the user, we
	-- use that; if not, we choose one of four functions depending on the
	-- options chosen. This is so that we don't have to call the options table
	-- every time the function is called.
	--]]
	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

	--[[
	-- Set up the args, metaArgs and nilArgs tables. args will be the one
	-- accessed from functions, and metaArgs will hold the actual arguments. Nil
	-- arguments are memoized in nilArgs, and the metatable connects all of them
	-- together.
	--]]
	local args, metaArgs, nilArgs, metatable = {}, {}, {}, {}
	setmetatable(args, metatable)

	local function mergeArgs(iterator, tables)
		--[[
		-- Accepts multiple tables as input and merges their keys and values
		-- into one table using the specified iterator. If a value is already
		-- present it is not overwritten; tables listed earlier have precedence.
		-- We are also memoizing nil values, but those values can be
		-- overwritten.
		--]]
		for _, t in ipairs(tables) do
			for key, val in iterator(t) do
				if metaArgs[key] == nil then
					local tidiedVal = tidyVal(key, val)
					if tidiedVal == nil then
						nilArgs[key] = true
					else
						metaArgs[key] = tidiedVal
					end
				end
			end
		end
	end

	--[[
	-- Define metatable behaviour. Arguments are memoized in the metaArgs table,
	-- and are only fetched from the argument tables once. Fetching arguments
	-- from the argument tables is the most resource-intensive step in this
	-- module, so we try and avoid it where possible. For this reason, nil
	-- arguments are also memoized, in the nilArgs table. Also, we keep a record
	-- in the metatable of when pairs and ipairs have been called, so we do not
	-- run pairs and ipairs on the argument tables more than once. We also do
	-- not run ipairs on fargs and pargs if pairs has already been run, as all
	-- the arguments will already have been copied over.
	--]]

	metatable.__index = function (t, key)
		--[[
		-- Fetches an argument when the args table is indexed. First we check
		-- to see if the value is memoized, and if not we try and fetch it from
		-- the argument tables. When we check memoization, we need to check
		-- metaArgs before nilArgs, as both can be non-nil at the same time.
		-- If the argument is not present in metaArgs, we also check whether
		-- pairs has been run yet. If pairs has already been run, we return nil.
		-- This is because all the arguments will have already been copied into
		-- metaArgs by the mergeArgs function, meaning that any other arguments
		-- must be nil.
		--]]
		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
				nilArgs[key] = true
			else
				metaArgs[key] = argTableVal
				return argTableVal
			end
		end
		return nil
	end

	metatable.__newindex = function (t, key, val)
		-- This function is called when a module tries to add a new value to the
		-- args table, or tries to change an existing value.
		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
			--[[
			-- If the argument is to be overwritten with nil, we need to erase
			-- the value in metaArgs, so that __index, __pairs and __ipairs do
			-- not use a previous existing value, if present; and we also need
			-- to memoize the nil in nilArgs, so that the value isn't looked
			-- up in the argument tables if it is accessed again.
			--]]
			metaArgs[key] = nil
			nilArgs[key] = true
		else
			metaArgs[key] = val
		end
	end

	metatable.__pairs = function ()
		-- Called when pairs is run on the args table.
		if not metatable.donePairs then
			mergeArgs(pairs, argTables)
			metatable.donePairs = true
			metatable.doneIpairs = true
		end
		return pairs(metaArgs)
	end

	metatable.__ipairs = function ()
		-- Called when ipairs is run on the args table.
		if not metatable.doneIpairs then
			mergeArgs(ipairs, argTables)
			metatable.doneIpairs = true
		end
		return ipairs(metaArgs)
	end

	return args
end

return arguments