模块:Arguments
本页面原来自中文维基百科页面模块:Arguments,依 CC BY-SA 3.0 授权引入。 经过双方编者的修改,本页面可能已与来源有很大差异。 |
此模块提供了对通过{{#invoke:}}
(以下简称#invoke)传递参数的简单处理。它是一个元模块,只能被其他模块所使用,而不应被#invoke直接调用。其特性如下:
- 对参数的简易修整,移除空白参数。
- 参数可以在当前框架或父框架中同时传递。(具体见下)
- 参数可以直接通过其他Lua模块或调试控制台传递。
- 参数按需获取,这样可以避免一些模板:Tag标签的问题。
- 可自定义更多特性。
基本用法
首先,您需要通过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
但实践中,最好先用专门的函数(指变量模板:Code的一个域值,如下文中的p._main)来处理一个特定的参数表(lua的表,与#invoke无关),然后用另一个函数(如下文中的p.main)调用这个函数,并将调用#invoke时传用的参数(即下文中的getArgs(frame))作为调用这个函数时的参数。这样,其他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)
-- 主模块放这儿,这里的args是一个纯粹的表
end
return p
如果你需要多个函数使用这些参数,而且你希望这些函数可用于#invoke,你可以使用包装函数(wrapper function)。
local getArgs = require('Module:Arguments').getArgs
local function makeInvokeFunc(funcName)
return function (frame)
local args = getArgs(frame)
return p[funcName](args)
end
end
local p = {}
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:A wrapper template',
'Template:Another wrapper template'
},
readOnly = true,
noOverwrite = true
})
移除参数空格和移除空白的参数
Blank arguments often trip up coders new to converting MediaWiki templates to Lua. 在模板中,空白字符串和仅包含空白字符(whitespace,空格、换行等)的字符串被视为假值(false)。然而,在Lua,空白字符串和只包含空白字符的字符串则会被视为真值(true)。这就是说,如果你在写Lua模块时,不注意这些参数,你可能会把本想视为假值的东西视为真值。为了避免这种情况,这个模块默认会移除所有的空白参数。
与之类似,空白字符在处理位置参数(positional arguments)时会发生问题。虽然来自#invoke的命了名的参数的空白字符会被剔除(trim),但是对一些位置参数仍然保留。大多数时候,多余的空白字符是不需要的,所以这个模块默认剔除这些空白字符。
然而,有时输入时又需要使用这些空白字符,或者空白参数。This can be necessary to convert some templates exactly as they were written. 如果你需要不剔除空白字符或移除空白参数,你可以(在调用getArgs函数时)将trim
和removeBlanks
参数设为false
。
local args = getArgs(frame, {
trim = false,
removeBlanks = false
})
对参数进行自定义格式化
有时,你需要移除某些空白参数,但是还有些空白参数又不想移除,或者,你需要将所有位置参数转化为小写字母。你可以使用valueFunc
选项。这个参数的值必须是一个接收两个参数key
和value
并且只返回一个值的函数,这个值是你在模板:Code表中索引名为模板:Code的域时得到的值。
例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.main
和p._main
函数,或者类似。
模板:Cot 例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
})
而且,请注意,每次有一个参数被模板:Code表请求时,valueFunc
函数都会迟早调用。So if you care about performance you should make sure you aren't doing anything inefficient with your code.
框架与父框架
args
表中的参数可以从当前框架或父框架同时传递。这句话有点难懂,可以看下面的例子。假设我们有个称为模块:ExampleArgs
的模块,这个模块输出(print)前两个传入的位置参数。
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 |
有三个选项可以用来改变行为:frameOnly
、parentOnly
和parentFirst
。如果设置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 |
注意:
- 如果你同时设置了
frameOnly
和parentOnly
两个选项,模块将不会从#invoke获取任何参数。这显然不是你需要的。 - 有时,父框架可能无效,比如getArgs是从父框架传入的,而不是当前框架。这种情况下,只有框架参数会被使用(除非设置了parentOnly,那种情况下不会使用任何参数),而且
parentFirst
和frameOnly
选项都会没有效果。
Wrappers
The wrappers option is used to specify a limited number of templates as wrapper templates, that is, templates whose only purpose is to call a module. If the module detects that it is being called from a wrapper template, it will only check for arguments in the parent frame; otherwise it will only check for arguments in the frame passed to getArgs. This allows modules to be called by either #invoke or through a wrapper template without the loss of performance associated with having to check both the frame and the parent frame for each argument lookup.
For example, the only content of Template:Side box (excluding content in 模板:Tag tags) is {{#invoke:Side box|main}}
. There is no point in checking the arguments passed directly to the #invoke statement for this template, as no arguments will ever be specified there. We can avoid checking arguments passed to #invoke by using the parentOnly option, but if we do this then #invoke will not work from other pages either. If this were the case, the 模板:Para in the code {{#invoke:Side box|main|text=Some text}}
would be ignored completely, no matter what page it was used from. By using the wrappers
option to specify 'Template:Side box' as a wrapper, we can make {{#invoke:Side box|main|text=Some text}}
work from most pages, while still not requiring that the module check for arguments on the Template:Side box page itself.
Wrappers can be specified either as a string, or as an array of strings.
local args = getArgs(frame, {
wrappers = 'Template:Wrapper template'
})
local args = getArgs(frame, {
wrappers = {
'Template:Wrapper 1',
'Template:Wrapper 2',
-- Any number of wrapper templates can be added here.
}
})
Notes:
- The module will automatically detect if it is being called from a wrapper template's /sandbox subpage, so there is no need to specify sandbox pages explicitly.
- The wrappers option effectively changes the default of the frameOnly and parentOnly options. If, for example, parentOnly were explicitly set to false with wrappers set, calls via wrapper templates would result in both frame and parent arguments being loaded, though calls not via wrapper templates would result in only frame arguments being loaded.
- If the wrappers option is set and no parent frame is available, the module will always get the arguments from the frame passed to
getArgs
.
Writing to the args table
Sometimes it can be useful to write new values to the args table. This is possible with the default settings of this module. (However, bear in mind that it is usually better coding style to create a new table with your new values and copy arguments from the args table as needed.)
args.foo = 'some value'
It is possible to alter this behaviour with the readOnly
and noOverwrite
options. If readOnly
is set then it is not possible to write any values to the args table at all. If noOverwrite
is set, then it is possible to add new values to the table, but it is not possible to add a value if it would overwrite any arguments that are passed from #invoke.
Ref tags
This module uses metatables to fetch arguments from #invoke. This allows access to both the frame arguments and the parent frame arguments without using the pairs()
function. This can help if your module might be passed 模板:Tag tags as input.
As soon as 模板:Tag tags are accessed from Lua, they are processed by the MediaWiki software and the reference will appear in the reference list at the bottom of the article. If your module proceeds to omit the reference tag from the output, you will end up with a phantom reference - a reference that appears in the reference list, but no number that links to it. This has been a problem with modules that use pairs()
to detect whether to use the arguments from the frame or the parent frame, as those modules automatically process every available argument.
This module solves this problem by allowing access to both frame and parent frame arguments, while still only fetching those arguments when it is necessary. The problem will still occur if you use pairs(args)
elsewhere in your module, however.
已知限制
元表(metatable)的使用也有其缺点。大多数正常Lua表工具都不会对args表正常工作,包括#
操作符号、next()
函数和表库(table library)中的函数。如果这对你的模块重要,你需要使用你自己的用来处理参数的函数,而不是这个模块。
-- 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
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 {}
--[[
-- Set up argument translation.
--]]
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
--[[
-- 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 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
-- 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(tables)
--[[
-- Accepts multiple tables as input and merges their keys and values
-- into one table. If a value is already present it is not overwritten;
-- tables listed earlier have precedence. We are also memoizing nil
-- values, which can be overwritten if they are '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
--[[
-- 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.
--]]
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)
-- 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 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
--[[
-- 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] = '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
-- Skip this one. This is a tail call, so this won't cause stack overflow
return translatenext(invariant)
else
return backtranslate, v
end
end
end
metatable.__pairs = function ()
-- Called when pairs is run on the args table.
if not metatable.donePairs then
mergeArgs(argTables)
metatable.donePairs = true
end
return translatenext, { t = metaArgs }
end
local function inext(t, i)
-- This uses our __index metamethod
local v = t[i + 1]
if v ~= nil then
return i + 1, v
end
end
metatable.__ipairs = function (t)
-- Called when ipairs is run on the args table.
return inext, t, 0
end
return args
end
return arguments