Module:Arguments: Difference between revisions

From NvWiki
Jump to navigation Jump to search
m 1 revision imported: Import modules used with Template:Infobox software
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
{{talk header}}
-- This module provides easy processing of arguments passed to Scribunto from
{{copied|from=Module:Arguments|from_oldid=696500078|to=:incubator:Module:Wp/nod/Arguments|to_diff=4236992}}
-- #invoke. It is intended for use by other Lua modules, and should not be
== Iterator corruption ==
-- called from #invoke directly.
{{ping|Mr. Stradivarius}} I found a subtle iterator corruption bug in this module.<syntaxhighlight lang="lua">
local args = require('Module:Arguments').getArgs(frame)
for k, v in args do
mw.log(k .. '=' .. (v or 'nil') .. ' ')
if args[k .. 'somesuffix'] then
  mw.log('Found suffix for ' .. k)
end
end
</syntaxhighlight> Attempting to read the somesuffix argument causes it to be memoized, adding it to the internal table, which apparently can corrupt the iterator and causes some arguments to be skipped. I've noticed this is only reproducible some of the time. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 02:58, 13 April 2014 (UTC)


:{{ping|Jackmcbarn}} That's a good find. (I assume that should be <code>pairs(args)</code> on line 2 rather than just <code>args</code>?) We're running into the undefined behaviour mentioned in the [[mw:Extension:Scribunto/Lua reference manual#next|next function docs]]: "''Behavior is undefined if, when using next for traversal, any non-existing key is assigned a value.''" The way that the __pairs metamethod works in this module means that all the existing arguments will have been memoized before the user gets a chance to index the args table. So if a user queries an existing argument during the pairs iteration, there will be no problem, as it will already be present in the metaArgs table. The error occurs when the user queries a non-existing argument. The __index function is set up to memoize this in metaArgs as nilArg, a blank table. That means that it is possible to add these blank tables as new values to the metaArgs table, even after all the non-nil values have been copied over from the frame objects. I've put a [https://en.wikipedia.org/w/index.php?title=Module:Arguments/sandbox&diff=604023029&oldid=590465333 fix in the sandbox] for this that uses the metatable.donePairs flag to check whether or not the arguments have been copied across. If they have already been copied across, then the __index metamethod won't memoize nils at all. While this fixes the bug, not memoizing the nils might cause adverse performance for some modules. Take a look and see what you think. Also, maybe [[User:Anomie|Anomie]] would like to check the fix before we put it up live? — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 16:02, 13 April 2014 (UTC)
local libraryUtil = require('libraryUtil')
::{{ping|Mr. Stradivarius}} Yes, that should have been pairs(args). What about a flag that gets set while you're inside the pairs method, and while it's set, it memoizes nils to some other table, then when the flag gets unset, it moves them to where they really go? Also, related, if an argument is an empty string, it gets iterated over even if empty strings get converted to nils, which is unexpected. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 17:47, 13 April 2014 (UTC)
local checkType = libraryUtil.checkType
::{{ping|Mr. Stradivarius}} I realized it's impossible for an iterator function to tell when it stops iterating (since the function calling it can return early, etc.), so that idea was out. Instead, I changed the way nils are memoized. They go to a different table now, which should solve that problem and the other problem at the same time. Thoughts? [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 23:14, 13 April 2014 (UTC)
::Once I got that implemented, I had another idea. Once pairs runs, we don't need to worry about memoizing at all anymore, because everything from argTables we'll ever look at is already part of metaArgs at that point. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 23:51, 13 April 2014 (UTC)
:::I think we should memoize after pairs runs, because users might query new keys that have nil values, and also because memoizing things the same way every time is simpler. I like your idea of using a nilArgs table rather than just putting a blank table in metaArgs. That will solve the iterator problem and allow us to use the same memoization scheme whether we have used pairs or not. Also, blank strings shouldn't be iterated over unless they are explicitly allowed, due to the way the mergeArgs function works (unless you found a bug in that as well?) — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 05:25, 14 April 2014 (UTC)
::::After running pairs, though, you don't need to check argTables anymore, so it's not worth memoizing nil to nilArg, since you can just return nil either way. Won't the code in the sandbox right now work right? [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 18:28, 14 April 2014 (UTC)
:::::Ah yes, you're quite right. I wasn't registering the fact that the new check meant that we bypassed the argTables check. I've added a comment and updated the module - hopefully everything should work now. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 08:12, 15 April 2014 (UTC)
::::::{{ping|Jackmcbarn}} Oops - we have been forgetting the problem of arguments being iterated over even if they are empty strings which get converted to nils. This would be solved by a nilArgs table, but is still present in the current version. I'll try and switch back to the nilArgs table version while keeping the formatting. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 19:47, 15 April 2014 (UTC)
:::::::{{ping|Mr. Stradivarius}} Now that I think about nilArgs, I don't really like it since it's an extra table lookup. Maybe if nilArg is found while iterating, just skip it and go on to the next element (or change all nilArg to nil once we're in pairs). [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 19:49, 15 April 2014 (UTC)
{{od|7}} I've implemented the nilArgs version in the sandbox. I think it is quite an elegant solution, despite being an extra table lookup. Skipping nilArg tables while iterating isn't easy, as we would need to implement an iterator inside of an iterator for each of __pairs and __ipairs. And changing all nilArg tables to nil once we are in pairs would mean we would have to run pairs on metaArgs after running mergeArgs to catch all of the nilArg tables that have been introduced by __index and __newindex. Using nilArgs to memoize avoids these problems and makes the code quite a bit shorter (take a look at the new __pairs and __ipairs functions). — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 20:24, 15 April 2014 (UTC)
:{{ping|Mr. Stradivarius}} Okay, I guess I'm sold on it. I think I see a few subtle bugs, though; let me see if I can track them down. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 20:35, 15 April 2014 (UTC)
::Thanks for taking a look at it. If I have time tomorrow, I may rewrite the test cases in [[mw:Extension:Scribunto/Lua reference manual#frame:newChild|the way foretold in the fine manual]]. That should make tracking these subtle bugs slightly less hit-and-miss. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 20:59, 15 April 2014 (UTC)
::Also, I [https://en.wikipedia.org/w/index.php?title=Module%3AArguments%2Fsandbox&diff=604357645&oldid=604349477 found a bug] in my code: __newindex wasn't properly overwriting nil arguments in metaArgs, which would have caused problems for both __pairs and __index. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 21:11, 15 April 2014 (UTC)
:::{{ping|Jackmcbarn}} I've finished rewriting [[Module:Arguments/testcases]], and I've also added some bad input tests and some iterator tests. I've tried my best to break it, but all the tests have passed so far. As expected, the main module fails four of the iterator tests. Are there any other ways you can think to break it? If not, I think it is time to update the main module. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 13:10, 17 April 2014 (UTC)
::::{{ping|Mr. Stradivarius}} Looks good. I did add one extra check for performance reasons. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 18:47, 17 April 2014 (UTC)


== Wrapper templates ==
local arguments = {}


{{edit protected|Module:Arguments|answered=yes}}
-- Generate four different tidyVal functions, so that we don't have to check the
<!-- Begin request -->
-- options every time we call it.
Please make the changes at [[Special:Diff/604718144/611675481]]. This adds support for the "wrappers" option. When set, it causes it to process parent arguments only if the parent is a wrapper, or frame arguments only otherwise.
<!-- End request -->
[[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 00:25, 5 June 2014 (UTC)
:Perhaps [[User:Mr. Stradivarius]] could check your code and apply this. &mdash;&nbsp;Martin <small>([[User:MSGJ|MSGJ]]&nbsp;·&nbsp;[[User talk:MSGJ|talk]])</small> 09:12, 5 June 2014 (UTC)
::{{ping|Jackmcbarn}} I'm not quite understanding what it means to say "if the parent is a wrapper". What kind of wrapper are we talking about? I can see that it would make sense to not try and index frame:getParent() if it's going to return nil sometimes, but the only time I can see this happening is if you call frame:getParent() on the current frame and then pass the parent frame to getArgs. Then again, there is probably something I'm missing, and I imagine that getting my head round this wrapper business will clear things up. As for general code review, {{code|1=local title, found = parent:getTitle(), false|2=lua}} seems a little dangerous to me. That would break if for some reason frame:getTitle ever switches to outputting two values (unlikely, but possible), so I would put those statements on separate lines. Also, we should probably check that <code>options.wrappers</code> is a table, so that we can give people a more informative error message if they specify something like {{code|1={wrappers = true}|2=lua}}. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 09:43, 5 June 2014 (UTC)
:::{{ping|Mr. Stradivarius}} A wrapper is a template that just calls a module, like [[Template:Infobox]] is a wrapper for [[Module:Infobox]] and [[Template:Edit protected]] is a wrapper for [[Module:Protected edit request]]. That's unrelated to the nil issue; I just fixed that at the same time since I had to modify that part of the code anyway. You're right that the main time getParent() would be nil is if you'd already called getParent() once, but the other time is if you call a module with a real frame through the console. I fixed the locals on the same line. Instead of throwing an error on non-tables, I made it turn it into a table, to handle the (very) common case where a module only has one wrapper. New diff is [[Special:Diff/604718144/611678252]]. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 13:31, 5 June 2014 (UTC)
::::Ah, I see what this is doing now. So if getArgs is called from a wrapper template, and that wrapper is listed in options.wrappers, it only loads the parent args, thereby saving a lookup in the frame args each time a new argument is requested from the client module. And if the parent frame isn't listed in options.wrappers it assumes that a user is calling the client module directly through #invoke. That sounds like a useful feature to add. One thing I was wondering - would it complicate the code too much to not call frame:getParent() if options.frameOnly is set? I'm not sure how expensive frame:getParent is to call, but I think it would make sense to not call it if we don't have to. (But then again, frameOnly isn't used that much as an option in my experience.) — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 00:57, 6 June 2014 (UTC)
:::::{{ping|Mr. Stradivarius}} I've made it do that. New diff is [[Special:Diff/604718144/611759842]]. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 01:11, 6 June 2014 (UTC)
::::::I've found one more optimization. [[Special:Diff/604718144/611760186]]. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 01:13, 6 June 2014 (UTC)
:::::::I've added some comments: [[Special:Diff/604718144/611784069]]. The code and the test cases look good to me, so if you're happy with this then I think we're ready to update the main module. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 06:23, 6 June 2014 (UTC)
::::::::{{ping|Mr. Stradivarius}} I'm happy with it. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 13:58, 6 June 2014 (UTC)
:::::::::[[File:Yes check.svg|20px|link=]] '''Done'''<!-- Template:EP --> Ok, it's updated. Let me know if you spot any issues with it. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 14:25, 6 June 2014 (UTC)


== Protected edit request on 5 July 2014 ==
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


{{edit protected|Module:Arguments|answered=yes}}
local function tidyValTrimOnly(key, val)
<!-- Begin request -->
if type(val) == 'string' then
Please make [[Special:Diff/615649711|these changes]]. This allows wrappers to still give both sets of arguments in either of the cases if such behavior is explicitly requested, while still preventing the double lookup in the other case.
return val:match('^%s*(.-)%s*$')
<!-- End request -->
else
[[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 03:30, 5 July 2014 (UTC)
return val
:[[File:Yes check.svg|20px|link=]] '''Done'''<!-- Template:EP --> If you could update the documentation too, that would be great. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 03:54, 5 July 2014 (UTC)
end
end


== Integrating with Lua ==
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


I'm thinking of integrating this module into Scribunto, the same way [[Module:HtmlBuilder]] was, but to do that, it needs to be released under a different license. {{ping|Mr. Stradivarius}} {{ping|Anomie}} Do you both agree to release your contributions to this module under the [[GNU General Public License]] v2 or newer (GPL v2+)? [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 14:58, 3 September 2014 (UTC)
local function tidyValNoChange(key, val)
: Sure. [[User:Anomie|Anomie]][[User talk:Anomie|⚔]] 15:07, 3 September 2014 (UTC)
return val
: Yes, that's fine with me. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 21:50, 3 September 2014 (UTC)
end
::I've submitted [[gerrit:158323]] that will add this to Scribunto. Note the following differences between this module and what I submitted:
::*Instead of taking a frame and an options table, it now takes only an options table, and <code>frame</code> is one of its options. This makes it a standard named-arguments function.
::*When wrappers aren't in use, it behaves as if <code>frameOnly</code> were set by default. Indiscriminate mixing of frame and parent arguments without knowing what the parent is has caused subtle bugs in the past, and it doesn't appear to have any legitimate use cases.
::*If you want just the parent arguments, pass <code>frame:getParent()</code> in place of <code>frame</code> when calling it. The <code>parentOnly</code> option has been removed.
::*When wrappers are in use, if the caller specifically requests frame arguments in addition to parent arguments (via <code>wrappersUseFrame</code>), the parent arguments always have precedence.
::{{ping|Mr. Stradivarius}} ping. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 21:09, 4 September 2014 (UTC)


== Implement i18n ==
local function matchesTitle(given, title)
 
local tp = type( given )
Should this module implement [[Internationalization and localization|i18n]]? Eg. by allowing a second parameter (boolean), which will make it try to load a name-map from a sub-module. Eg. Bananas could use
return (tp == 'string' or tp == 'number') and mw.title.new( given ).prefixedText == title
args = getArgs(frame, true)['firstname']
end
and Bananas/i18n_de could contain
return { vorname = 'firstname' }
(I admit a mapping in the opposit direction is more intuitive, but this allows multiple parameternames to be mapped to the same lua-parameter.)
[[User:Poul G|Poul G]] ([[User talk:Poul G|talk]]) 09:40, 1 November 2014 (UTC)
: The danger with this idea is that it makes it more difficult to use the module in different languages, unless everyone uses the canonical name anyway. For example, "Spezial:Beobachtungsliste" works if you go to [[:de:Spezial:Beobachtungsliste|dewiki]] but doesn't [[Spezial:Beobachtungsliste|here]] or most other-language wikis, while "Special:Watchlist" will work everywhere. [[User:Anomie|Anomie]][[User talk:Anomie|⚔]] 14:38, 1 November 2014 (UTC)
:: [[wikt:WTF|WhyTF]] do we have a soft-redirect at [[Spezial:Beobachtungsliste]]? [[User:Anomie|Anomie]][[User talk:Anomie|⚔]] 14:45, 1 November 2014 (UTC)
:Well, our user-editors in non-english languages should have access to templates in their native language. But at the same time it would be a great advantage to share the logic in the Lua-modules. Which implies that a translation is needed; it could be in the template <code><nowiki>{{#Person:name|firstname={{{vorname|}}}|...}}</nowiki></code> or be hidden in i18n within the modules configuration. (Maybe it was a mistake to open this on the site, where translation isn't needed.) [[User:Poul G|Poul G]] ([[User talk:Poul G|talk]]) 12:45, 3 November 2014 (UTC)
Is it possible this module to handle named parameters with Unicode names like: <pre>{{my_template | unnamed_1 | параметър = 123 | named_2 = ... etc.}}</pre>--[[User:Pl71|Pl71]] ([[User talk:Pl71|talk]]) 15:32, 24 February 2016 (UTC)


== Pairs bug ==
local translate_mt = { __index = function(t, k) return k end }


I've just found a bug in the pairs code of this module. It turns out that if you delete a value in the args table by setting it to nil, the the value is still present if you access the args table with pairs. There's a demonstration of the bug in [https://en.wikipedia.org/w/index.php?title=Module:User:Mr._Stradivarius/sandbox&oldid=637283032 my sandbox], and I've added two new test cases which are [[Module talk:Arguments/testcases|currently failing]].
function arguments.getArgs(frame, options)
checkType('getArgs', 1, frame, 'table', true)
checkType('getArgs', 2, options, 'table', true)
frame = frame or {}
options = options or {}


To fix this, it looks like we would need a new table to memoize nils. We need to check whether an argument has been expressly deleted when calling mergeArgs, but at the same time, values in nilArgs need to be overwritable for precedence whitespace arguments to work properly. I don't see how one table can fulfil both functions.
--[[
-- 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


Alternatively, we could get away with using one nilArgs table if we change the module to only ever check the frame args or the parent frame args, and never both. If I remember rightly, this is what the proposed getArgs function inside Scribunto does, so if that solution seems better we could wait for that function to be deployed and then switch all of our existing modules over to it. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 06:59, 9 December 2014 (UTC)
--[[
:{{ping|Mr. Stradivarius}} There is one edge case in the new getArgs function that would still read both, so that won't save us. However, I did find a way to make it work without adding an additional table. It's in the sandbox. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 15:11, 9 December 2014 (UTC)
-- Get the argument tables. If we were passed a valid frame object, get the
::{{ping|Jackmcbarn}} Yes, that looks like a good approach to solving it - definitely better than introducing another table. Instead of using trinary logic, how about using strings to denote the status, similar to what Lua does with __mode? I think that would make the code more readable. We could use 'hard' and 'soft' for hard and soft nils, or just 'h' and 's' if we want to be concise. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 15:55, 9 December 2014 (UTC)
-- frame arguments (fargs) and the parent frame arguments (pargs), depending
:::{{ping|Mr. Stradivarius}} Okay, that's done. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 04:11, 10 December 2014 (UTC)
-- on the options set and on the parent frame's availability. If we weren't
::::{{ping|Jackmcbarn}} Looks good. Unless there's anything else you would like to change, I think we're ok to update the main module now. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 04:37, 10 December 2014 (UTC)
-- passed a valid frame object, we are being called from another Lua module
:::::{{ping|Mr. Stradivarius}} Actually, there is, but I can't do it yet. Once the inexpensive mw.title.new change gets here, I want to make this use mw.title.new to normalize wrapper names (to make less stuff break when our modules get transwikied to wikis with different namespace names). [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 03:57, 11 December 2014 (UTC)
-- or from the debug console, so assume that we were passed a table of args
::::::{{ping|Jackmcbarn}} Ok, but I don't think there's any need to wait for that before we fix the current bug. I'll go and update the module now. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr. Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪ talk ♪]]</sup> 04:27, 11 December 2014 (UTC)
-- directly, and assign it to a new variable (luaArgs).
:::::::Okay. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 04:28, 11 December 2014 (UTC)
--]]
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


== Ipairs bug ==
-- 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


{{ping|Mr. Stradivarius}} I discovered that calling ipairs() on args and then breaking out of the loop early will unnecessarily result in all of the numeric arguments being expanded, instead of just the ones that were iterated over. I added a test case for this and implemented a fix in the sandbox. Can you take a look at it? If it looks good, I'll add it (along with the other change waiting in the sandbox) to the main module. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 05:32, 28 December 2014 (UTC)
-- Set the order of precedence of the argument tables. If the variables are
:{{ping|Jackmcbarn}} Sorry for the delay in replying. Actually, yesterday and today I've been a bit ill, so I don't really trust myself to do code reviews right now. I'll take a look at this when I have my higher brain functions back, or if you want to go ahead and implement your fix anyway, that's fine by me. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 06:27, 29 December 2014 (UTC)
-- 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


== Document argument translation system ==
--[[
-- 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


Hi [[User:Jackmcbarn |Jackmcbarn ]]!
--[[
-- 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)


Could you please add documentation about [[Special:Diff/668829606|this]]? [[User:He7d3r|Helder]] 11:12, 1 September 2015 (UTC)
local function mergeArgs(tables)
:I'd rather not encourage its use right now, since a better but incompatible way will become available soon. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 19:23, 1 September 2015 (UTC)
--[[
::@[[User:Jackmcbarn|Jackmcbarn]]: can you provide more details about that? Where is that new way being developed? [[User:He7d3r|Helder]] 19:30, 13 September 2015 (UTC)
-- Accepts multiple tables as input and merges their keys and values
:::{{ping|He7d3r}} It's already written; it's just awaiting approval. You can see it at [[gerrit:158323]]. [[User:Jackmcbarn|Jackmcbarn]] ([[User talk:Jackmcbarn|talk]]) 20:58, 13 September 2015 (UTC)
-- 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


[[User:Jackmcbarn|Jackmcbarn]], [[User:He7d3r|Helder]], now that we have [[mw:Help:Tabular_Data|tabular data support]], we can easily implement global translations. I already started on doing it with the [[mw:Module:TNT|TNT module]]. It allows a module or a template to be copied anywhere without modifications, and all localization is done in one place on Commons. This means we can introduce parameter localization as well, without any core changes. Let me know if you want to help with it :) --[[User:Yurik|Yurik]] ([[User talk:Yurik|talk]]) 03:46, 13 January 2017 (UTC)
--[[
:Cool! I'll keep an eye on that. [[User:He7d3r|Helder]] 12:01, 18 January 2017 (UTC)
-- 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.
--]]


== Using ipairs ==
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


I have not seriously used Module:Arguments so now that I'm looking at how it is used in [[Module:Team appearances list]], I am puzzled about the default options and <code>ipairs</code>. What happens if a module does the following?
metatable.__newindex = function (t, key, val)
<syntaxhighlight lang="lua">
-- This function is called when a module tries to add a new value to the
local getArgs = require('Module:Arguments').getArgs
-- args table, or tries to change an existing value.
local args = getArgs(frame) -- where frame is from a template invoke
if type(key) == 'string' then
for i, v in ipairs(args) do
key = options.translate[key]
    print(i, v)
end
end
if options.readOnly then
</syntaxhighlight>
error(
I gather that works as expected with something like <code><nowiki>{{example|one|two|three}}</nowiki></code> (and it would trim any leading/trailing whitespace from each parameter).
'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


However, it would only process "one" in <code><nowiki>{{example|one||three}}</nowiki></code> because the blank second parameter would be converted to nil, and that would terminate ipairs.
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


Does that mean that anything using Module:Arguments with a variable number of numeric arguments must use something like <code>compressSparseArray</code> from [[Module:TableTools]] (or set options to not trim/remove parameters)? If that is true, I would have thought it would be mentioned in the documentation here. Did an early version of Module:Arguments default to removing blank parameters so ipairs processes each provided numeric parameter (that's what I thought happened)? [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 03:25, 18 November 2016 (UTC)
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


:{{re|Johnuniq}} Have you found a solution for this problem? —&nbsp;[[User:UnladenSwallow|UnladenSwallow]] ([[User talk:UnladenSwallow|talk]]) 01:58, 15 May 2020 (UTC)
local function inext(t, i)
::{{re|UnladenSwallow}} I haven't looked for a solution because I don't like obscure layers. Module:Arguments appears to be very efficient although it appears to do quite a lot of work, yet it seems unnecessary overhead to me. I've never needed the module and I don't know if the above is a problem now. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 03:38, 15 May 2020 (UTC)
-- This uses our __index metamethod
local v = t[i + 1]
if v ~= nil then
return i + 1, v
end
end


==Help in writing better testcases==
metatable.__ipairs = function (t)
Hello developers, I am working with [[mw:Multilingual Templates and Modules]] and to convert this module into a shared one, we need better [[mw:Module:Arguments/testcases]]. Can anyone please help? {{ping|Frietjes|RexxS|Johnuniq|Mr. Stradivarius|Anomie|Xaosflux|Ans|Jackmcbarn|Jonesey95}} [[User:Capankajsmilyo|Capankajsmilyo]]<sup>([[User talk:Capankajsmilyo|Talk]] | [[Wikipedia:WikiProject Infoboxes/assistance|Infobox assistance]])</sup> 10:39, 22 May 2019 (UTC)
-- Called when ipairs is run on the args table.
return inext, t, 0
end


== The Arguments cannot contain "="? ==
return args
 
end
see test case: [[Module:Sandbox/shizhao/test]],[[Module talk:Sandbox/shizhao/test]]。 If arguments contain "=", Lua error: bad argument #1 to 'match' (string expected, got nil).--[[User:Shizhao|Shizhao]] ([[User talk:Shizhao|talk]]) 15:45, 21 January 2020 (UTC)
:@[[User:Shizhao|Shizhao]] That's just standard procedure: in a template or module invoke, a parameter like <code>aaa=bbb</code> appears as a named parameter with value <code>bbb</code> for parameter <code>aaa</code>. See the fix in [[Special:Diff/936934897|diff]]. I haven't looked at what [[Module:Sandbox/shizhao/test]] is for but please don't fuss with a signature—keeping them simple is best and editors should not need a module for a private purpose. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 22:43, 21 January 2020 (UTC)
 
== Using frame and parentFrame simultaneously ==
 
There is a wrapper template [[Template:Authority control (arts)]] which uses <code><nowiki>{{#invoke:Authority control|show=arts}}</nowiki></code>
 
And on articles, I want to use {{tlx|Authority control (arts)|2=show=CZ,ES}} to show additional things.
 
I would like to concatenate both of these values in a comma-separated list, i.e. <code>show = arts,CZ,ES}</code> Is that something which is possible with this module? &mdash;&nbsp;Martin <small>([[User:MSGJ|MSGJ]]&nbsp;·&nbsp;[[User talk:MSGJ|talk]])</small> 13:17, 20 January 2023 (UTC)
:{{ping|MSGJ}} No - this module can get values from both the frame and the parent frame, but one will take priority and overwrite the other. You will need custom logic to do what you want to do. — '''''[[User:Mr. Stradivarius|<span style="color: #194D00; font-family: Palatino, Times, serif">Mr.&nbsp;Stradivarius</span>]]''''' <sup>[[User talk:Mr. Stradivarius|♪&nbsp;talk&nbsp;♪]]</sup> 13:56, 20 January 2023 (UTC)
::Okay, thought so. Thanks for the reply. &mdash;&nbsp;Martin <small>([[User:MSGJ|MSGJ]]&nbsp;·&nbsp;[[User talk:MSGJ|talk]])</small> 15:16, 20 January 2023 (UTC)
 
== Case-insensitive arguments ==
 
I've had a request for case-insensitive argument names in a client module of Module:Arguments over on enWS (that imports from enWP). Specific case requested was treating {{para|Volume}} as equivalent to {{para|volume}} (there are historical reasons why contributors on enWS expect that to work). At first blush that looks really messy to implement in the client module, and at the same time it seems like something that 1) would be best implemented as an option (ala. {{para|trim}} or {{para|removeBlanks}}) in Module:Arguments and 2) would be generally useful for other clients of Module:Arguments. Without digging too deep I imagine it'd really be "canonical casing", i.e. just internally lower-casing all provided argument names when the option is set.
 
Thoughts? [[User:Xover|Xover]] ([[User talk:Xover|talk]]) 06:25, 31 May 2023 (UTC)
 
== writing to the args table when the key contains a hyphen (-) ==


[[Module:Infobox military conflict]] tries to include [[Module:Infobox mapframe]], but we need to pass along some parameters. args.onByDefault seems to work, but args["mapframe-zoom"] doesn't seem to. Is this expected? --[[User:Joy|Joy]] ([[User talk:Joy|talk]]) 19:47, 23 October 2025 (UTC)
return arguments

Latest revision as of 16:57, 26 December 2025

Template:Used in system Template:Module rating Template:Cascade-protected template

This module provides easy processing of arguments passed from #invoke. It is a meta-module, meant for use by other modules, and should not be called from #invoke directly (for a module directly invocable by templates you might want to have a look at Template:Ml). Its features include:

  • Easy trimming of arguments and removal of blank arguments.
  • Arguments can be passed by both the current frame and by the parent frame at the same time. (More details below.)
  • Arguments can be passed in directly from another Lua module or from the debug console.
  • Most features can be customized.

Basic use

First, you need to load the module. It contains one function, named getArgs.

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

In the most basic scenario, you can use getArgs inside your main function. The variable args is a table containing the arguments from #invoke. (See below for details.)

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

function p.main(frame)
	local args = getArgs(frame)
	-- Main module code goes here.
end

return p

Recommended practice

However, the recommended practice is to use a separate function as the entry point from #invoke just for processing the arguments. This allows other Lua modules to call your core logic directly, improving performance by avoiding the overhead of interacting with the frame object.

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)
	-- Main module code goes here.
end

return p

The way this is called from a template is {{#invoke:Example|main}} (optionally with some parameters like {{#invoke:Example|main|arg1=value1|arg2=value2}}), and the way this is called from a module is require('Module:Example')._main({arg1 = 'value1', arg2 = value2, 'spaced arg3' = 'value3'}). What this second one does is construct a table with the arguments in it, then gives that table to the p._main(args) function, which uses it natively.

Multiple functions

If you want multiple functions to use the arguments, and you also want them to be accessible from #invoke, you can use a 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)
	-- Code for the first function goes here.
end

p.func2 = makeInvokeFunc('_func2')

function p._func2(args)
	-- Code for the second function goes here.
end

return p

Options

The following options are available. They are explained in the sections below.

local args = getArgs(frame, {
	trim = false,
	removeBlanks = false,
	valueFunc = function (key, value)
		-- Code for processing one argument
	end,

	frameOnly = true,
	parentOnly = true,
	parentFirst = true,

	wrappers = {
		'Template:A wrapper template',
		'Template:Another wrapper template'
	},

	readOnly = true,
	noOverwrite = true
})

Trimming whitespace

MediaWiki trims whitespace for named arguments coming from #invoke or a template call, but preserves whitespace for positional arguments. By default, this module helps trim whitespace also for position arguments. To preserve whitespace for positional arguments, set the trim option to false.

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

When the valueFunc option is given, the valueFunc function will be responsible for trimming whitespace, and the trim option will have no effect.

Removing blank arguments

"Blank arguments" are arguments from #invoke or template that are blank strings or consist of only whitespace. By default, this module removes all blank arguments. To preserve the blank arguments, set the removeBlanks option to false.

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

This might be necessary for some templates' operation.

Note: When converting MediaWiki templates to Lua, keep in mind that in Lua, blank strings and strings consisting only of whitespace are considered true. If you don't pay attention to such blank arguments when you write your Lua modules, you might treat something as true that should actually be treated as false.

When the valueFunc option is given, the valueFunc function will be responsible for handling blank arguments, and the removeBlanks option will have no effect.

Custom formatting of arguments

Sometimes you want to remove some blank arguments but not others, or perhaps you might want to put all of the positional arguments in lower case. To do things like this you can use the valueFunc option. The input to this option must be a function that takes two parameters, key and value, and returns a single value. This value is what you will get when you access the field key in the args table.

Example 1: this function preserves whitespace for the first positional argument's value, but trims all other arguments' value and removes all other blank arguments.

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
})

Example 2: this function removes blank arguments and converts all argument values to lower case, but doesn't trim whitespace from positional parameters.

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
})

Note: the above functions will fail if passed input that is not of type string or nil. This might be the case if you use the getArgs function in the main function of your module, and that function is called by another Lua module. In this case, you will need to check the type of your input. This is not a problem if you are using a function specially for arguments from #invoke (i.e. you have p.main and p._main functions, or something similar).

Template:Cot Example 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
})

Example 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
})

Template:Cob

Also, please note that the valueFunc function is called more or less every time an argument is requested from the args table, so if you care about performance you should make sure you aren't doing anything inefficient with your code.

Frames and parent frames

Arguments in the args table can be passed from the current frame or from its parent frame at the same time. To understand what this means, it is easiest to give an example. Let's say that we have a module called Module:ExampleArgs. This module prints the first two positional arguments that it is passed.

Template:Cot

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

Template:Cob

Template:ExampleArgs contains the code {{#invoke:ExampleArgs|main|''firstInvokeArg''}}.

Now if we were to call Template:ExampleArgs, the following would happen:

Code Result
{{#invoke:ExampleArgs|main|''firstInvokeArg''}}

(call #invoke directly without template)
firstInvokeArg

(call #invoke directly without template)

{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstInvokeArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstInvokeArg secondTemplateArg

There are three options you can set to change this behaviour: frameOnly, parentOnly and parentFirst. If you set frameOnly then only arguments passed from the current frame will be accepted; if you set parentOnly then only arguments passed from the parent frame will be accepted; and if you set parentFirst then arguments will be passed from both the current and parent frames, but the parent frame will have priority over the current frame. Here are the results in terms of Template:ExampleArgs:

frameOnly
Code Result
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstInvokeArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstInvokeArg
parentOnly
Code Result
{{ExampleArgs}}
{{ExampleArgs|firstTemplateArg}} firstTemplateArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstTemplateArg secondTemplateArg
parentFirst
Code Result
{{ExampleArgs}} firstInvokeArg
{{ExampleArgs|firstTemplateArg}} firstTemplateArg
{{ExampleArgs|firstTemplateArg|secondTemplateArg}} firstTemplateArg secondTemplateArg

Notes:

  1. If you set both the frameOnly and parentOnly options, the module won't fetch any arguments at all from #invoke. This is probably not what you want.
  2. In some situations a parent frame may not be available, e.g. if getArgs is passed the parent frame rather than the current frame. In this case, only the frame arguments will be used (unless parentOnly is set, in which case no arguments will be used) and the parentFirst and frameOnly options will have no effect.

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 Template: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 |text=Some text 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.
	}
})

The wrappers option changes the default behaviors of the frameOnly and parentOnly options.

Notes:

  1. 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.
  2. 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.

Notes

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 Template:Tag tags as input.

As soon as Template: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 without any number linking 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.

Known limitations

The use of metatables also has its downsides. Most of the normal Lua table tools won't work properly on the args table, including the # operator, the next() function, and the functions in the table library. If using these is important for your module, you should use your own argument processing function instead of this module.

See also


-- 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