Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Arguments

From The Petit Planet Wiki
Revision as of 06:11, 9 November 2025 by Stevium (talk | contribs) (Created Module:Arguments)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

-- Source: https://genshin-impact.fandom.com/wiki/Module:Arguments

--- Arguments invocation argument extractor for Scribunto modules. -- It is intended for use by other Lua modules, and should not be -- called from an invocation (`#invoke`) directly. -- -- This module supports the following features: -- * Trimming and blank argument removal. -- * Argument inheritance between child and parent frames. -- * Argument extraction for external modules and console input. -- * Options to customise argument extraction behaviour. -- -- @script arguments -- @release stable -- @note The `args` table from the @{arguments.getArgs} -- function is a metatable for performance reasons. -- Thus, the table will not permit Lua table methods -- such as `#args`, @{next|next(args)}, and @{table} -- library functions. -- @note This module will eventually be adapted as a -- library in MediaWiki -- core, called as `require('getArgs')`. The core -- library will remove `options.parentOnly`. -- @author Mr. Stradivarius (Wikipedia) -- @author Anomie (Wikipedia) -- @author Jackmcbarn (Wikipedia) -- @author Dessamator -- @author DarthKitty -- @attribution Module:Arguments (Wikipedia) -- @see Original module on Wikipedia -- @see Test cases for this module local arguments = {}

-- Module dependencies. local i18n = require('Module:I18n').loadMessages('Arguments') local util = require('libraryUtil') local checkType = util.checkType

local SANDBOX_PATTERN = '/Draft$'

-- Four different value tidying functions. -- This way, we don't have to check the options every time we call them.

--- Default value tidying function. -- Trims parameter values automatically if they are defined strings. -- Treats blank strings as `nil`. -- @function tidyValDefault -- @param {string|number} key MediaWiki parameter key. -- @param {string|nil} val MediaWiki parameter value, -- or nil if `key` is an empty string or nil. -- @local 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

--- Value tidying function that trims values. -- Trims parameter values automatically if they are defined strings. -- @function tidyValTrimOnly -- @param {string|number} key MediaWiki parameter key. -- @param {string|nil} val MediaWiki parameter value. -- @local local function tidyValTrimOnly(key, val)

   if type(val) == 'string' then
       return val:match('^%s*(.-)%s*$')
   else
       return val
   end

end

--- Value tidying function that removes blanks. -- Removes blank values from the arguments table. -- @function tidyValRemoveBlanksOnly -- @param {string|number} key MediaWiki parameter key. -- @param {string|nil} val MediaWiki parameter value, -- or nil if `key` is whitespace or nil. -- @local 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

--- Value tidying function that returns original value. -- Effectively a NOOP function that does no value processing. -- @function tidyValNoChange -- @param {string|number} key MediaWiki parameter key. -- @param {string|nil} val MediaWiki parameter value. -- @local local function tidyValNoChange(key, val)

   return val

end

--- Parent template title match checker. -- @function matchesTitle -- @param {string|number|nil} given Local prefixed page -- title, or MediaWiki article ID (`wgArticleId`). -- @param {string} title Title of parent template. -- @return {boolean} Whether the `given` ID/title matches -- the title of the parent template. local function matchesTitle(given, title)

   local tp = type( given )
   return (tp == 'string' or tp == 'number') and mw.title.new( given ).prefixedText == title

end

--- Default argument translation metatable. -- @table translate_mt -- @local local translate_mt = { __index = function(t, k) return k end }

--- Main argument extraction utility. -- Arguments are memoized once fetched for optimal performance, -- as with the `frame.args` metatable in Scribunto core. -- -- The default argument lookup behaviour uses the child frame arguments -- first, then the parent frame arguments. There are numerous frame -- options to change this behaviour. -- -- The default value tidying behaviour trims parameter values if they -- are defined strings and treats blank strings as `nil`. This can be -- customised in the `getArgs` options. -- -- @param {frame|table} frame Scribunto frame object or -- Lua arguments table, passed from an invocation -- or Lua logic such as `frame:getParent()`. -- If this parameter does not have an `args` field -- and a `getParent` method, `frame` is assumed -- to be a Lua arguments table, such as the -- arguments from a named arguments call. -- @param[opt] {table} options Extraction/processing options. -- @param[opt] {boolean} options.trim -- Whether to trim the blank arguments present in -- the arguments table. Accepts `false` only. -- Default: `true`. -- @param[opt] {boolean} options.removeBlanks -- Whether to remove blank arguments from the -- arguments table. Does not shift sequential -- arguments removed by the processing stage. -- Accepts `false` only. Default: `true`. -- @param[opt] {function} options.valueFunc -- Custom value tidying function for use if the -- `trim` and `removeBlanks` options don't cover -- the developer's argument processing use case. -- @param[opt] {boolean} options.frameOnly -- Only read arguments from child frame (the -- `frame` parameter - usually invocation frame). -- @param[opt] {boolean} options.parentOnly -- Only read arguments from `frame` parent (the -- `frame` parameter - usually template frame). -- @param[opt] {boolean} options.parentFirst -- Argument lookup in the `frame` parent first, -- prioritised over the invocation frame arguments. -- @param[opt] {table} options.wrappers -- Individual value or array of values, listing -- wrapper title name(s) or article ID(s) to permit -- parent argument lookup from. -- @param[opt] {string|number} options.wrapper -- Alias of `options.wrappers` - contains title -- name or article ID to permit parent argument -- lookup from. -- @param[opt] {boolean} options.readOnly -- Whether to restrict write permissions to the -- arguments table. When set to a truthy value, -- an error will be thrown on any write attempt. -- @param[opt] {boolean} options.noOverwrite -- Whether to restrict overwrite attempts on -- existing argument keys in the arguments table. -- When set to a truthy value, an error will be -- thrown on any write attempt that would result -- in an existing argument being overwritten. -- @param[opt] {table} options.translate -- Map of parameter name aliases to their canonical -- argument parameter names. -- @param[opt] {table} options.backtranslate -- Map of canonical parameter names to their -- argument parameter aliases. -- Supersedes `options.translate` if both options -- are in use. -- @error[opt,317] 'bad value assigned to option "valueFunc" -- (function expected, got $type)' -- @error[opt,407] 'could not write to argument table key "$key"; -- the table is read-only' -- @error[opt,409] 'could not write to argument table key "$key"; -- overwriting existing arguments is not permitted' -- @return {table} Arguments extracted from invocation. -- The argument data is embedded as a metatable in -- the exported table and cannot be accessed with -- the `#` operator or @{table} library methods. -- However, the exported table can be written to if -- the `options.readOnly` flag parameter is not -- truthy. -- @usage -- -- local getArgs = require('Module:Arguments').getArgs -- function p.main(frame) -- local args = getArgs(frame, { -- wrapper = 'Template:<TEMPLATE>' -- }) -- -- Use the args table here. -- -- A common paradigm is `return p._main(args)`. -- -- This allows other Lua modules to access the -- -- main logic in a performant manner without a -- -- frame object. -- end -- -- @note Reference tags in the form of `<ref>` will -- generate phantom references when calling the -- @{pairs} iterator on the arguments table, -- **IF** the `<ref>` tag does not appear in the -- dependent module's wikitext output. 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
   options.wrappers = options.wrappers or options.wrapper
   if
       type(frame.args) == 'table' and
       type(frame.getParent) == 'function'
   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.
       -- The arguments will be fetched from the parent frame if
       -- the parent frame's title is present in options.wrapper;
       -- otherwise it will look up arguments in the frame object
       -- passed to getArgs.
       if options.wrappers then
           local parent = frame:getParent()
           if not parent then
               fargs = frame.args
           else
               local title = parent:getTitle():gsub(SANDBOX_PATTERN, )
               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
       -- When options.wrapper isn't set, check the other options.
       else
           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(i18n:msg('error-value-func', 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)
   -- 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).
   local function mergeArgs(tables)
       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.
   -- 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.
   metatable.__index = function (t, key)
       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(i18n:msg('error-write-permission', tostring(key)), 2)
       elseif options.noOverwrite and args[key] ~= nil then
           error(i18n:msg('error-overwrite-permission', tostring(key)), 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
   -- This metamethod is called when pairs is run on the args table.
   metatable.__pairs = function ()
       if not metatable.donePairs then
           mergeArgs(argTables)
           metatable.donePairs = true
       end
       return translatenext, { t = metaArgs }
   end
   -- This custom `ipairs`-style iterator uses our __index metamethod.
   local function inext(t, i)
       local v = t[i + 1]
       if v ~= nil then
           return i + 1, v
       end
   end
   -- This metamethod is called when ipairs is run on the args table.
   metatable.__ipairs = function (t)
       return inext, t, 0
   end
   return args

end

return arguments