模組:Arguments
![]() | 此模組被引用於約4,150,000個頁面,佔全部頁面的52%。 為了避免造成大規模的影響,所有對此模組的編輯應先於沙盒或測試樣例上測試。 測試後無誤的版本可以一次性地加入此模組中,但是修改前請務必於討論頁發起討論。 模板引用數量會自動更新。 |
![]() | 本模組已與en:Module:Arguments保持全域手動同步。 請注意,若您修改本地的模組,將可能影響同步。 |
此模組是呼叫其他模組的模組。
此模組提供了對透過{{#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),但是對一些位置參數仍然保留。大多數時候,多餘的空白字元是不需要的,所以這個模組預設剔除這些空白字元。
然而,有時輸入時又需要使用這些空白字元,或者需要保留空白參數。把某些模板準確地轉化為模組時,可能有必要這麼做。如果你需要這樣,你可以將trim
和removeBlanks
參數設為false
。
local args = getArgs(frame, {
trim = false,
removeBlanks = false
})
對參數進行自訂格式化
有時,你需要移除某些空白參數,但是還有些空白參數又不想移除,或者,你需要將所有位置參數轉化為小寫字母。你可以使用valueFunc
選項。這個參數的值必須是一個接收兩個參數key
和value
並且只返回一個值的函數,這個值是你在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.main
和p._main
函數,或者類似。
帶有資料類型檢查功能的例1和例2
|
---|
例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 |
有三個選項可以用來改變行為: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
選項都會沒有效果。
包裝
包裝(wrapper)選項用於指定一部分模板作為包裝模板(wrapper templates),也就是說,要呼叫模組的模板。如果模組檢測到是被包裝模板呼叫的,則只會檢查父框架中的參數;否則,只檢查傳遞到getArgs的框架的參數。這允許模組要麼被#invoke呼叫,要麼透過包裝模板呼叫,而不會由於為每次參數尋找(argument lookup)同時檢查框架和父框架而損失效能。
比如,Template:Side box的內容(除了<noinclude>...</noinclude>
標籤內的)為{{#invoke:Side box|main}}
。檢查直接傳遞到模板的#invoke陳述式的參數是沒有道理的,因為這裏沒有指定參數。我們可以透過parentOnly選項避免檢查傳遞到#invoke的參數,但如果這樣做,#invoke也不會從其他頁面起作用。如果是這樣,代碼{{#invoke:Side box|main|text=Some text}}
中的|text=Some 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',
-- 可以在此处添加多个包装模板。
}
})
注意:
- 模組會自動檢測是否是從包裝模板的/sandbox子頁面呼叫的,所以不需要清楚地指定沙盒頁面。
- wrappers選項有效改變frameOnly和parentOnly的預設的選項。如果,比如,設置了wrappers時清楚地將parentOnly設為false,透過包裝模板呼叫會導致同時載入框架和父框架的參數,儘管非經由包裝模板的呼叫會導致只載入框架參數。
- 如果設置了wrappers但是沒有可用的父框架,模組總是會從傳遞給
getArgs
的框架中得到參數。
寫入參數列
有時給參數列寫入新值會很有用。這可以透過此模組的預設設置實現。(然而,記住最好的代碼風格是,將需要的參數列中的參數複製到一個新的表中。)
args.foo = '一些值'
可以帶有readOnly
和noOverwrite
選項修改此行為。如果設置了readOnly
,則完全不可能將任何值寫到參數列中。如果設置了noOverwrite
,則可以將新值添加到此表,但是如果需要重寫從#invoke傳遞的任何參數則不可能添加值。
ref標籤
模組使用元表以從#invoke中取得參數。這允許不使用pairs()
函數就取得框架參數和父框架參數。如果你需要將<ref>...</ref>
標籤作為輸入時,這會很有用。
<ref>...</ref>
標籤是從Lua中取得的,因此會被MediaWiki軟件處理,引用會在文章底部的參考文獻列表中顯示。如果模組繼續從輸出中省略索引標籤,則會產生一個假引用 —— 在參考文獻列表中顯示,但是沒有與之連結的數字。模組如果使用pairs()
來檢測是否從框架或父框架中使用參數,就會出現此問題,因為這些模組會自動處理每一個可用變量。
此模組允許既取得框架又取得父框架而僅在需要時取得這些參數,從而解決此問題。然而,模組其他位置使用pairs(args)
時,仍會出現此問題。
已知限制
元表(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