置顶公告:【置顶】关于临时开启评论区所有功能的公告(2022.10.22) | 【置顶】关于本站Widget恢复使用的公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。

Module:Arguments

贴贴♀百科,万娘皆可贴的百科全书!转载请标注来源页面的网页链接,并声明引自贴贴百科。内容不可商用。
跳到导航 跳到搜索
Template-info.svg 模块文档  [查看] [编辑] [历史] [刷新]

该模块来自于英语维基百科的同名模块,英文原文详见历史版本[1]

这个模块提供了从#invoke传递的参数的简单处理。它是一个元模块,用于其他模块,不应该直接从#Invoke调用。它的特点包括:

  • 简单地修剪参数和删除空白参数。
  • 参数可以同时由当前框架和父框架传递。
  • 参数可以直接从另一个Lua模块或调试控制台传入。
  • 参数是根据需要获取的,这有助于避免<ref>...</ref>标签带来的(一些)问题。
  • 大多数功能都可以定制。

基础使用

首先,你需要加载(load)本模块(module),它包含一个叫 getArgs 的函数。

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

最基本的情况下,您可以在主函数使用getArgs函数。变量args的类型是一个表(table),其中包含从#invoke传过来的参数(argument)。

  1. local getArgs = require('Module:Arguments').getArgs
  2. local p = {}
  3. function p.main(frame)
  4. local args = getArgs(frame)
  5. -- 主函数的代码会在这里运行。
  6. end
  7. return p

然而,我们更推荐使用一个函数单独用来处理来自于#invoke的参数。这意味着,如果别人用另一个Lua模块调用你的模块,你不必再次通过frame(框架)对象获取args变量。这会提高性能。

  1. local getArgs = require('Module:Arguments').getArgs
  2. local p = {}
  3. function p.main(frame)
  4. local args = getArgs(frame)
  5. return p._main(args)
  6. end
  7. function p._main(args)
  8. -- 主函数的代码。
  9. end
  10. return p

如果你想要从#invoke中访问多个函数,同时这些函数也可以使用#invoke的参数,你可以使用包装函数(wrapper function),就像下面的例子一样。

  1. local getArgs = require('Module:Arguments').getArgs
  2. local function makeInvokeFunc(funcName)
  3. return function (frame)
  4. local args = getArgs(frame)
  5. return p[funcName](args)
  6. end
  7. end
  8. local p = {}
  9. p.func1 = makeInvokeFunc('_func1')
  10. function p._func1(args)
  11. -- 第一个函数的代码。
  12. end
  13. p.func2 = makeInvokeFunc('_func2')
  14. function p._func2(args)
  15. -- 第二个函数的代码。
  16. end
  17. return p

参数选项

以下参数选项(option)是可用的,下文会对其进行解释。

  1. local args = getArgs(frame, {
  2. trim = false,
  3. removeBlanks = false,
  4. valueFunc = function (key, value)
  5. -- Code for processing one argument
  6. end,
  7. frameOnly = true,
  8. parentOnly = true,
  9. parentFirst = true,
  10. wrappers = {
  11. 'Template:A wrapper template',
  12. 'Template:Another wrapper template'
  13. },
  14. readOnly = true,
  15. noOverwrite = true
  16. })

空白参数和空格的处理

空白参数(blank argument)经常会让MediaWiki的编辑者产生困惑。因为在模板的语法,空白字符串和仅包括空格的字符串会被当做false处理。但是在Lua,空白字符串和仅包括空格的字符串会被处理为true。如果你在写Lua模块的时候没有对参数产生足够重视,你可能会把本应是false的变量当做true。为了避免这种情况,空白变量会被默认去除。

在处理位置参数的时候,空格可能会导致棘手的问题。对于已命名的参数,空格会被修剪(trim)掉。对于位置参数,虽然空格通常会保留,但是大多数时候空格是不需要的,所以模块也会默认修剪掉这些空格。

然而,你有时候可能需要空白参数作为输入内容,有时候也想要保留多出来的空格,比如想将模板的内容按照所写的那样精确转化时。这时,你可以把trim变量和removeBlanks(清除空白参数)变量设置为false

  1. local args = getArgs(frame, {
  2. trim = false,
  3. removeBlanks = false
  4. })

自定义参数格式

有时你可能需要去除特定的某些空白参数,或者你想要把所有的位置参数都变成小写字母。你可以使用valueFunc参数选项。这个参数选项必须是一个函数,且需要两个形参(parameters),分别是keyvalue,并且只返回一个值。这个返回值,就是args表(table)在指定键key下获取到的元素。

示例1:这个函数保留第一个参数的空格,但会修剪其它参数的空格,以及清除所有空白的参数。

  1. local args = getArgs(frame, {
  2. valueFunc = function (key, value)
  3. if key == 1 then
  4. return value
  5. elseif value then
  6. value = mw.text.trim(value)
  7. if value ~= '' then
  8. return value
  9. end
  10. end
  11. return nil
  12. end
  13. })

示例2:这个函数会清除所有的空白参数,并且把所有的参数都变成小写字母,但是不会修剪位置参数的空格。

  1. local args = getArgs(frame, {
  2. valueFunc = function (key, value)
  3. if not value then
  4. return nil
  5. end
  6. value = mw.ustring.lower(value)
  7. if mw.ustring.find(value, '%S') then
  8. return value
  9. end
  10. return nil
  11. end
  12. })

注意:如果传过来的输入既不是string类型,也不是nil,这个函数会失效。如果你的模块在main函数调用了getArgs函数,并且这个函数被其他Lua类调用的时候,很可能会出现这种情况。这时候,你应该检查输入的类型。但是,如果你有一个函数用来特别处理来自于#invoke的参数,不会出现这种问题(例如你使用了p.mainp._main函数,或者之类的)。


增加类型检查示例1/示例2

示例1:

  1. local args = getArgs(frame, {
  2. valueFunc = function (key, value)
  3. if key == 1 then
  4. return value
  5. elseif type(value) == 'string' then
  6. value = mw.text.trim(value)
  7. if value ~= '' then
  8. return value
  9. else
  10. return nil
  11. end
  12. else
  13. return value
  14. end
  15. end
  16. })

示例2:

  1. local args = getArgs(frame, {
  2. valueFunc = function (key, value)
  3. if type(value) == 'string' then
  4. value = mw.ustring.lower(value)
  5. if mw.ustring.find(value, '%S') then
  6. return value
  7. else
  8. return nil
  9. end
  10. else
  11. return value
  12. end
  13. end
  14. })

需要注意的是,每次从args表获取参数的时候,valueFunc都会大约被调用一次。为了确保性能,你需要保证你的代码的高效性。

框架和父框架

args中的参数既可以从当前框架传递,也可以从其父框架传递。我们来通过示例来理解上面这段话。我们有一个叫做Module:ExampleArgs的模块,它可以输出传入的前两个位置参数中。

Module:ExampleArgs
  1. local getArgs = require('Module:Arguments').getArgs
  2. local p = {}
  3. function p.main(frame)
  4. local args = getArgs(frame)
  5. return p._main(args)
  6. end
  7. function p._main(args)
  8. local first = args[1] or ''
  9. local second = args[2] or ''
  10. return first .. ' ' .. second
  11. end
  12. return p

然后,模块Module:ExampleArgs被模板Template:ExampleArgs调用。这个模板包括以下代码{{#invoke:ExampleArgs|main|firstInvokeArg}}

接下来,如果我们通过以下方式调用模板Template:ExampleArgs,会出现以下情况。

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

以下三种参数选项可以改变参数传递情况:frameOnlyparentOnly,和parentFirst。如果你设置frameOnly参数选项,那么只有从当前框架传递来的参数会被接受。如果你设置parentOnly参数选项,那么只有从父框架传递来的参数会被接受。如果你设置parentFirst参数选项,当前框架和父框架传递来的参数都会被接受,但是父框架的参数会被优先接受。下面是部分示例。


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参数不会起作用。 [1]

包装器

包装器参数选项可以规定少数模板作为包装模板(wrapper template)。也就是说,这些模板唯一的作用就是去调用一个模块。如果这个模块发现是自己被包装模板调用的,它会只检查父框架的参数,否则,它会只检查传递给getArgs的参数。这允许模块既可以被#invoke调用,也可以被包装模板调用。在模块被包装模板调用的时候,并不会因为既需要检查当前框架,也需要检查父框架,而带来性能损失。


我们以Template:Side box模板举例,这个模板的有效内容[2]仅有{{#invoke:Side box|main}}这一小段。如果仅对于这个模板,我们可以使用parentOnly参数选项来优化(因为这样可以避免参数检查),但是这样会造成很大的问题,可能会影响其它的页面。比如,其他页面出现的{{#invoke:Side box|main|text=Some text}}中的|text=Some text会被完全忽略。而使用wrappers参数选项,把模板'Template:Side box'包装为包装模板,就可以避免这个问题。

wrappers参数选项既可以被指定为一个字符串,也可以被指定为字符串数组。


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


  1. local args = getArgs(frame, {
  2. wrappers = {
  3. 'Template:Wrapper 1',
  4. 'Template:Wrapper 2',
  5. -- Any number of wrapper templates can be added here.
  6. }
  7. })

注意:

  1. 模块会自动地检测自己是被包装模板调用还是被沙盒子页面调用。所以无需显式指定沙盒页面。
  2. wrappers参数选项会在特定时候有效地改变frameOnlyparentOnly。例如,如果将parentOnly显式设置为false,并设置了wrappers,通过包装模板的调用,当前框架和父框架都会被加载,而没有通过包装模板的调用仅仅会调用当前框架。
  3. 如果wrappers被设定,但并没有可用的父框架,模块一直从当前框架获取参数传递给getArgs

对args表的写入

有时你可能需要在args表中写入新的值。模块的默认设定允许你这样做。(通常,创建一个新表,从args表复制需要的参数,是更好的编程习惯)

  1. args.foo = 'some value'

你可以通过readOnlynoOverwrite参数选项来监测对args的写入。如果设定了readOnly,那么args表无法被写入。如果设定了noOverwrite,那么args表可以被写入,但是从#invoke传入的参数无法被修改。

ref标签

这个模块使用元表来从#invoke中获取参数。这样可以无需pairs()函数,同时获取当前框架和父框架的参数。这样你的模块可以接受<ref>...</ref>标签(tag)作为输入。

只要Lua可以访问到<ref>...</ref>标签(reference),它们就会被MediaWiki软件处理,然后会有引用内容在页面的底部(注释与外部链接)出现。如果模块的输出省略了<ref>...</ref>标签,会触发一个bug:在引用列表会出现这个引用内容,但是没有数字连接到这个引用。这对于使用pairs()来检测是使用当前框架还是父框架的参数的模块来说是一个问题,因为这些模块会自动处理每个可用的参数。

然而本模块解决了这个问题,通过同时访问当前框架和父框架,仅在必要的时候获取参数。但是如果你的模块使用pairs(args)了,这个问题还是会出现。

本模块已知的缺陷

元表(metatable)的使用带来的副作用。Lua中大多数的table tools并不会正常运行在args表上。包括#运算符,next()函数,还有table library中的函数。如果你需要在你的模块用到这些,你应该用自己的函数来处理参数而不是这个模块。

  1. 这一段可能需要看完后面的部分才能够理解,翻译者注。
  2. 指的是除了<noinclude>...</noinclude>的内容
  1. -- This module provides easy processing of arguments passed to Scribunto from
  2. -- #invoke. 该模块旨在为其他Lua模块所用,它不应该被#invoke直接调用。
  3. local libraryUtil = require('libraryUtil')
  4. local checkType = libraryUtil.checkType
  5. local arguments = {}
  6. -- Generate four different tidyVal functions, so that we don't have to check the
  7. -- options every time we call it.
  8. local function tidyValDefault(key, val)
  9. if type(val) == 'string' then
  10. val = val:match('^%s*(.-)%s*$')
  11. if val == '' then
  12. return nil
  13. else
  14. return val
  15. end
  16. else
  17. return val
  18. end
  19. end
  20. local function tidyValTrimOnly(key, val)
  21. if type(val) == 'string' then
  22. return val:match('^%s*(.-)%s*$')
  23. else
  24. return val
  25. end
  26. end
  27. local function tidyValRemoveBlanksOnly(key, val)
  28. if type(val) == 'string' then
  29. if val:find('%S') then
  30. return val
  31. else
  32. return nil
  33. end
  34. else
  35. return val
  36. end
  37. end
  38. local function tidyValNoChange(key, val)
  39. return val
  40. end
  41. local function matchesTitle(given, title)
  42. local tp = type( given )
  43. return (tp == 'string' or tp == 'number') and mw.title.new( given ).prefixedText == title
  44. end
  45. local translate_mt = { __index = function(t, k) return k end }
  46. function arguments.getArgs(frame, options)
  47. checkType('getArgs', 1, frame, 'table', true)
  48. checkType('getArgs', 2, options, 'table', true)
  49. frame = frame or {}
  50. options = options or {}
  51. --[[
  52. -- Set up argument translation.
  53. --]]
  54. options.translate = options.translate or {}
  55. if getmetatable(options.translate) == nil then
  56. setmetatable(options.translate, translate_mt)
  57. end
  58. if options.backtranslate == nil then
  59. options.backtranslate = {}
  60. for k,v in pairs(options.translate) do
  61. options.backtranslate[v] = k
  62. end
  63. end
  64. if options.backtranslate and getmetatable(options.backtranslate) == nil then
  65. setmetatable(options.backtranslate, {
  66. __index = function(t, k)
  67. if options.translate[k] ~= k then
  68. return nil
  69. else
  70. return k
  71. end
  72. end
  73. })
  74. end
  75. --[[
  76. -- Get the argument tables. If we were passed a valid frame object, get the
  77. -- frame arguments (fargs) and the parent frame arguments (pargs), depending
  78. -- on the options set and on the parent frame's availability. If we weren't
  79. -- passed a valid frame object, we are being called from another Lua module
  80. -- or from the debug console, so assume that we were passed a table of args
  81. -- directly, and assign it to a new variable (luaArgs).
  82. --]]
  83. local fargs, pargs, luaArgs
  84. if type(frame.args) == 'table' and type(frame.getParent) == 'function' then
  85. if options.wrappers then
  86. --[[
  87. -- The wrappers option makes Module:Arguments look up arguments in
  88. -- either the frame argument table or the parent argument table, but
  89. -- not both. This means that users can use either the #invoke syntax
  90. -- or a wrapper template without the loss of performance associated
  91. -- with looking arguments up in both the frame and the parent frame.
  92. -- Module:Arguments will look up arguments in the parent frame
  93. -- if it finds the parent frame's title in options.wrapper;
  94. -- otherwise it will look up arguments in the frame object passed
  95. -- to getArgs.
  96. --]]
  97. local parent = frame:getParent()
  98. if not parent then
  99. fargs = frame.args
  100. else
  101. local title = parent:getTitle():gsub('/sandbox$', '')
  102. local found = false
  103. if matchesTitle(options.wrappers, title) then
  104. found = true
  105. elseif type(options.wrappers) == 'table' then
  106. for _,v in pairs(options.wrappers) do
  107. if matchesTitle(v, title) then
  108. found = true
  109. break
  110. end
  111. end
  112. end
  113. -- We test for false specifically here so that nil (the default) acts like true.
  114. if found or options.frameOnly == false then
  115. pargs = parent.args
  116. end
  117. if not found or options.parentOnly == false then
  118. fargs = frame.args
  119. end
  120. end
  121. else
  122. -- options.wrapper isn't set, so check the other options.
  123. if not options.parentOnly then
  124. fargs = frame.args
  125. end
  126. if not options.frameOnly then
  127. local parent = frame:getParent()
  128. pargs = parent and parent.args or nil
  129. end
  130. end
  131. if options.parentFirst then
  132. fargs, pargs = pargs, fargs
  133. end
  134. else
  135. luaArgs = frame
  136. end
  137. -- Set the order of precedence of the argument tables. If the variables are
  138. -- nil, nothing will be added to the table, which is how we avoid clashes
  139. -- between the frame/parent args and the Lua args.
  140. local argTables = {fargs}
  141. argTables[#argTables + 1] = pargs
  142. argTables[#argTables + 1] = luaArgs
  143. --[[
  144. -- Generate the tidyVal function. If it has been specified by the user, we
  145. -- use that; if not, we choose one of four functions depending on the
  146. -- options chosen. This is so that we don't have to call the options table
  147. -- every time the function is called.
  148. --]]
  149. local tidyVal = options.valueFunc
  150. if tidyVal then
  151. if type(tidyVal) ~= 'function' then
  152. error(
  153. "bad value assigned to option 'valueFunc'"
  154. .. '(function expected, got '
  155. .. type(tidyVal)
  156. .. ')',
  157. 2
  158. )
  159. end
  160. elseif options.trim ~= false then
  161. if options.removeBlanks ~= false then
  162. tidyVal = tidyValDefault
  163. else
  164. tidyVal = tidyValTrimOnly
  165. end
  166. else
  167. if options.removeBlanks ~= false then
  168. tidyVal = tidyValRemoveBlanksOnly
  169. else
  170. tidyVal = tidyValNoChange
  171. end
  172. end
  173. --[[
  174. -- Set up the args, metaArgs and nilArgs tables. args will be the one
  175. -- accessed from functions, and metaArgs will hold the actual arguments. Nil
  176. -- arguments are memoized in nilArgs, and the metatable connects all of them
  177. -- together.
  178. --]]
  179. local args, metaArgs, nilArgs, metatable = {}, {}, {}, {}
  180. setmetatable(args, metatable)
  181. local function mergeArgs(tables)
  182. --[[
  183. -- Accepts multiple tables as input and merges their keys and values
  184. -- into one table. If a value is already present it is not overwritten;
  185. -- tables listed earlier have precedence. We are also memoizing nil
  186. -- values, which can be overwritten if they are 's' (soft).
  187. --]]
  188. for _, t in ipairs(tables) do
  189. for key, val in pairs(t) do
  190. if metaArgs[key] == nil and nilArgs[key] ~= 'h' then
  191. local tidiedVal = tidyVal(key, val)
  192. if tidiedVal == nil then
  193. nilArgs[key] = 's'
  194. else
  195. metaArgs[key] = tidiedVal
  196. end
  197. end
  198. end
  199. end
  200. end
  201. --[[
  202. -- Define metatable behaviour. Arguments are memoized in the metaArgs table,
  203. -- and are only fetched from the argument tables once. Fetching arguments
  204. -- from the argument tables is the most resource-intensive step in this
  205. -- module, so we try and avoid it where possible. For this reason, nil
  206. -- arguments are also memoized, in the nilArgs table. Also, we keep a record
  207. -- in the metatable of when pairs and ipairs have been called, so we do not
  208. -- run pairs and ipairs on the argument tables more than once. We also do
  209. -- not run ipairs on fargs and pargs if pairs has already been run, as all
  210. -- the arguments will already have been copied over.
  211. --]]
  212. metatable.__index = function (t, key)
  213. --[[
  214. -- Fetches an argument when the args table is indexed. First we check
  215. -- to see if the value is memoized, and if not we try and fetch it from
  216. -- the argument tables. When we check memoization, we need to check
  217. -- metaArgs before nilArgs, as both can be non-nil at the same time.
  218. -- If the argument is not present in metaArgs, we also check whether
  219. -- pairs has been run yet. If pairs has already been run, we return nil.
  220. -- This is because all the arguments will have already been copied into
  221. -- metaArgs by the mergeArgs function, meaning that any other arguments
  222. -- must be nil.
  223. --]]
  224. if type(key) == 'string' then
  225. key = options.translate[key]
  226. end
  227. local val = metaArgs[key]
  228. if val ~= nil then
  229. return val
  230. elseif metatable.donePairs or nilArgs[key] then
  231. return nil
  232. end
  233. for _, argTable in ipairs(argTables) do
  234. local argTableVal = tidyVal(key, argTable[key])
  235. if argTableVal ~= nil then
  236. metaArgs[key] = argTableVal
  237. return argTableVal
  238. end
  239. end
  240. nilArgs[key] = 'h'
  241. return nil
  242. end
  243. metatable.__newindex = function (t, key, val)
  244. -- This function is called when a module tries to add a new value to the
  245. -- args table, or tries to change an existing value.
  246. if type(key) == 'string' then
  247. key = options.translate[key]
  248. end
  249. if options.readOnly then
  250. error(
  251. 'could not write to argument table key "'
  252. .. tostring(key)
  253. .. '"; the table is read-only',
  254. 2
  255. )
  256. elseif options.noOverwrite and args[key] ~= nil then
  257. error(
  258. 'could not write to argument table key "'
  259. .. tostring(key)
  260. .. '"; overwriting existing arguments is not permitted',
  261. 2
  262. )
  263. elseif val == nil then
  264. --[[
  265. -- If the argument is to be overwritten with nil, we need to erase
  266. -- the value in metaArgs, so that __index, __pairs and __ipairs do
  267. -- not use a previous existing value, if present; and we also need
  268. -- to memoize the nil in nilArgs, so that the value isn't looked
  269. -- up in the argument tables if it is accessed again.
  270. --]]
  271. metaArgs[key] = nil
  272. nilArgs[key] = 'h'
  273. else
  274. metaArgs[key] = val
  275. end
  276. end
  277. local function translatenext(invariant)
  278. local k, v = next(invariant.t, invariant.k)
  279. invariant.k = k
  280. if k == nil then
  281. return nil
  282. elseif type(k) ~= 'string' or not options.backtranslate then
  283. return k, v
  284. else
  285. local backtranslate = options.backtranslate[k]
  286. if backtranslate == nil then
  287. -- Skip this one. This is a tail call, so this won't cause stack overflow
  288. return translatenext(invariant)
  289. else
  290. return backtranslate, v
  291. end
  292. end
  293. end
  294. metatable.__pairs = function ()
  295. -- Called when pairs is run on the args table.
  296. if not metatable.donePairs then
  297. mergeArgs(argTables)
  298. metatable.donePairs = true
  299. end
  300. return translatenext, { t = metaArgs }
  301. end
  302. local function inext(t, i)
  303. -- This uses our __index metamethod
  304. local v = t[i + 1]
  305. if v ~= nil then
  306. return i + 1, v
  307. end
  308. end
  309. metatable.__ipairs = function (t)
  310. -- Called when ipairs is run on the args table.
  311. return inext, t, 0
  312. end
  313. return args
  314. end
  315. return arguments