Module:GetArgs

--  Handles most of the boilerplate necessary to fetch arguments, such as trimming   leading/trailing whitespace, making blank arguments evaluate to false, correctly   choosing between current-frame and parent-frame, etc.   Originally written on the English Wikipedia by Mr. Stradivarius, Jackmcbarn, and Anomie.   Code released under the GPL v2+ as per:   https://en.wikipedia.org/w/index.php?diff=624020648&oldid=624019645   https://en.wikipedia.org/w/index.php?diff=624073793&oldid=624020648   @license GNU GPL v2+ local libraryUtil = require( 'libraryUtil' ) local sandboxSuffixPattern = string.format( '/%s$',  mw.message.new( 'scribunto-template-sandbox-subpage-name' ):plain:gsub("%p", "%%%0") ) -- Generate four different tidyVal functions, so that we don't have to check the options every -- time we call it. local function tidyValDefault( k, v ) if type( v ) == 'string' then v = v:match( '^%s*(.-)%s*$' ) if v == '' then return nil end end return v end local function tidyValTrimOnly( k, v ) if type( v ) == 'string' then return v:match( '^%s*(.-)%s*$' ) else return v  end end local function tidyValRemoveBlanksOnly( k, v ) if type( v ) == 'string' and not v:find( '%S' ) then return nil else return v  end end local function tidyValNoChange( k, v ) return v end return function( options ) libraryUtil.checkType( 'getArgs', 1, options, 'table', true ) local frame = options.frame if frame == nil then return {} end libraryUtil.checkTypeForNamedArg( 'getArgs', 'frame', frame, 'table' ) --  -- Get the argument tables. If we were passed a valid frame object, see if we're being   -- called via a wrapper. If so, then get pargs, the wrapper's arguments (and if   -- wrappersUseFrame is set, the frame's arguments as well). Otherwise, just get fargs,   -- the frame's arguments. 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 local parent = frame:getParent local found = false if parent then local title = parent:getTitle:gsub( sandboxSuffixPattern, '' ) if options.wrappers == title then found = true elseif type( options.wrappers ) == 'table' then -- If either a key or a value matches, it's a match if options.wrappers[title] ~= nil then found = true else for _,v in ipairs( options.wrappers ) do                          if v == title then found = true break end end end end if found then pargs = parent.args end end if not found or options.wrappersUseFrame then fargs = frame.args end else fargs = frame.args 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 = { pargs } argTables[#argTables + 1] = fargs 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 libraryUtil.checkTypeForNamedArg( 'getArgs', 'valueFunc', tidyVal, 'function' ) 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, fetchedArgs and nilArgs tables. args will be the one accessed from -- functions, and fetchedArgs will hold the actual arguments. Nil arguments are memoized -- in nilArgs, and args_mt connects all of them together. local args, fetchedArgs, nilArgs, args_mt = {}, {}, {}, {} local donePairs, doneIpairs = false, false local function mergeArgs( iterator, tables ) -- Accepts multiple tables as input and merges their keys and values into one -- table using the specified iterator. If a value is already present, it is not -- overwritten; tables listed earlier have precedence. We are also memoizing nil -- values, but those values can be overwritten. for _, t in ipairs( tables ) do          for key, val in iterator( t ) do               if fetchedArgs[key] == nil then local tidiedVal = tidyVal( key, val ) if tidiedVal == nil then nilArgs[key] = true else fetchedArgs[key] = tidiedVal end end end end end -- Define metatable behaviour. Arguments are memoized in the fetchedArgs 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. function args_mt.__index( 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 fetchedArgs before -- nilArgs, as both can be non-nil at the same time. If the argument is not -- present in fetchedArgs, 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 fetchedArgs by the mergeArgs function, -- meaning that any other arguments must be nil. local val = fetchedArgs[key] if val ~= nil then return val elseif donePairs or nilArgs[key] then return nil end for _, argTable in ipairs( argTables ) do          local argTableVal = tidyVal( key, argTable[key] ) if argTableVal == nil then nilArgs[key] = true else fetchedArgs[key] = argTableVal return argTableVal end end return nil end function args_mt.__newindex( t, key, val ) if val == nil then -- If the argument is to be overwritten with nil, we need to memoize the -- nil in nilArgs, so that the value isn't looked up in the argument -- tables if it is accessed again. nilArgs[key] = true end fetchedArgs[key] = val end function args_mt.__pairs if not donePairs then donePairs = true mergeArgs( pairs, argTables ) end return pairs( fetchedArgs ) end function args_mt.__ipairs if not doneIpairs and not donePairs then doneIpairs = true mergeArgs( ipairs, argTables ) end return ipairs( fetchedArgs ) end return setmetatable( args, args_mt ) end