Module:GetArgs

Revision as of 23:45, 16 July 2015 by com>Ckwng (Protected "Module:GetArgs": High traffic page: This module is used by an extremely high number of pages. It is also based upon the getArgs built-in in Scribunto, so it should not be edited except to update. (‎[edit=autoconfirmed] (indefinite) ‎[mo)

Documentation for this module may be created at Module:GetArgs/doc

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