×
Create a new article
Write your page title here:
We currently have 1 article on Test2 Wiki. Type your article name above or create one of the articles listed here!



    Test2 Wiki

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

    -- Module:Args (https://dev.fandom.com/wiki/Module:Arguments) from Fandom Dev Wiki, used under CC-BY-SA
    
    
    --- 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 [[mw:gerrit:q/158323|MediaWiki
    --                      core]], called as `require('getArgs')`. The core
    --                      library will remove `options.parentOnly`.
    --  @author             [[wikipedia:User:Mr. Stradivarius|Mr. Stradivarius]] (Wikipedia)
    --  @author             [[wikipedia:User:Anomie|Anomie]] (Wikipedia)
    --  @author             [[wikipedia:User:Jackmcbarn|Jackmcbarn]] (Wikipedia)
    --  @author             [[User:Dessamator|Dessamator]]
    --  @author             [[User:DarthKitty|DarthKitty]]
    --  @attribution        [[wikipedia:Module:Arguments|Module:Arguments]] (Wikipedia)
    --  @see                [[wikipedia:Module:Arguments|Original module on Wikipedia]]
    --  @see                [[Module:Arguments/testcases|Test cases for this module]]
    local arguments = {}
    
    --  Module dependencies.
    local i18n = require('Dev:I18n').loadMessages('Arguments')
    local util = require('libraryUtil')
    local checkType = util.checkType
    
    -- 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$', '')
                    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
    
    Cookies help us deliver our services. By using our services, you agree to our use of cookies.

    Recent changes

  • Alexender • 2 months ago
  • Fjalon • 2 months ago
  • Fjalon • 2 months ago
  • Fjalon • 2 months ago
  • Cookies help us deliver our services. By using our services, you agree to our use of cookies.