Module:Arguments

-- 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.locallibraryUtil=require('libraryUtil')localcheckType=libraryUtil.checkTypelocalarguments={}-- Generate four different tidyVal functions, so that we don't have to check the-- options every time we call it.localfunctiontidyValDefault(key,val)iftype(val)=='string'thenval=val:match('^%s*(.-)%s*$')ifval==''thenreturnnilelsereturnvalendelsereturnvalendendlocalfunctiontidyValTrimOnly(key,val)iftype(val)=='string'thenreturnval:match('^%s*(.-)%s*$')elsereturnvalendendlocalfunctiontidyValRemoveBlanksOnly(key,val)iftype(val)=='string'thenifval:find('%S')thenreturnvalelsereturnnilendelsereturnvalendendlocalfunctiontidyValNoChange(key,val)returnvalendlocalfunctionmatchesTitle(given,title)localtp=type(given)return(tp=='string'ortp=='number')andmw.title.new(given).prefixedText==titleendlocaltranslate_mt={__index=function(t,k)returnkend}functionarguments.getArgs(frame,options)checkType('getArgs',1,frame,'table',true)checkType('getArgs',2,options,'table',true)frame=frameor{}options=optionsor{}--[[ -- Set up argument translation. --]]options.translate=options.translateor{}ifgetmetatable(options.translate)==nilthensetmetatable(options.translate,translate_mt)endifoptions.backtranslate==nilthenoptions.backtranslate={}fork,vinpairs(options.translate)dooptions.backtranslate[v]=kendendifoptions.backtranslateandgetmetatable(options.backtranslate)==nilthensetmetatable(options.backtranslate,{__index=function(t,k)ifoptions.translate[k]~=kthenreturnnilelsereturnkendend})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). --]]localfargs,pargs,luaArgsiftype(frame.args)=='table'andtype(frame.getParent)=='function'thenifoptions.wrappersthen--[[ -- 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. --]]localparent=frame:getParent()ifnotparentthenfargs=frame.argselselocaltitle=parent:getTitle():gsub('/sandbox$','')localfound=falseifmatchesTitle(options.wrappers,title)thenfound=trueelseiftype(options.wrappers)=='table'thenfor_,vinpairs(options.wrappers)doifmatchesTitle(v,title)thenfound=truebreakendendend-- We test for false specifically here so that nil (the default) acts like true.iffoundoroptions.frameOnly==falsethenpargs=parent.argsendifnotfoundoroptions.parentOnly==falsethenfargs=frame.argsendendelse-- options.wrapper isn't set, so check the other options.ifnotoptions.parentOnlythenfargs=frame.argsendifnotoptions.frameOnlythenlocalparent=frame:getParent()pargs=parentandparent.argsornilendendifoptions.parentFirstthenfargs,pargs=pargs,fargsendelseluaArgs=frameend-- 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.localargTables={fargs}argTables[#argTables+1]=pargsargTables[#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. --]]localtidyVal=options.valueFunciftidyValtheniftype(tidyVal)~='function'thenerror("bad value assigned to option 'valueFunc'"..'(function expected, got '..type(tidyVal)..')',2)endelseifoptions.trim~=falsethenifoptions.removeBlanks~=falsethentidyVal=tidyValDefaultelsetidyVal=tidyValTrimOnlyendelseifoptions.removeBlanks~=falsethentidyVal=tidyValRemoveBlanksOnlyelsetidyVal=tidyValNoChangeendend--[[ -- 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. --]]localargs,metaArgs,nilArgs,metatable={},{},{},{}setmetatable(args,metatable)localfunctionmergeArgs(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_,tinipairs(tables)doforkey,valinpairs(t)doifmetaArgs[key]==nilandnilArgs[key]~='h'thenlocaltidiedVal=tidyVal(key,val)iftidiedVal==nilthennilArgs[key]='s'elsemetaArgs[key]=tidiedValendendendendend--[[ -- 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. --]]iftype(key)=='string'thenkey=options.translate[key]endlocalval=metaArgs[key]ifval~=nilthenreturnvalelseifmetatable.donePairsornilArgs[key]thenreturnnilendfor_,argTableinipairs(argTables)dolocalargTableVal=tidyVal(key,argTable[key])ifargTableVal~=nilthenmetaArgs[key]=argTableValreturnargTableValendendnilArgs[key]='h'returnnilendmetatable.__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.iftype(key)=='string'thenkey=options.translate[key]endifoptions.readOnlythenerror('could not write to argument table key "'..tostring(key)..'"; the table is read-only',2)elseifoptions.noOverwriteandargs[key]~=nilthenerror('could not write to argument table key "'..tostring(key)..'"; overwriting existing arguments is not permitted',2)elseifval==nilthen--[[ -- 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]=nilnilArgs[key]='h'elsemetaArgs[key]=valendendlocalfunctiontranslatenext(invariant)localk,v=next(invariant.t,invariant.k)invariant.k=kifk==nilthenreturnnilelseiftype(k)~='string'ornotoptions.backtranslatethenreturnk,velselocalbacktranslate=options.backtranslate[k]ifbacktranslate==nilthen-- Skip this one. This is a tail call, so this won't cause stack overflowreturntranslatenext(invariant)elsereturnbacktranslate,vendendendmetatable.__pairs=function()-- Called when pairs is run on the args table.ifnotmetatable.donePairsthenmergeArgs(argTables)metatable.donePairs=trueendreturntranslatenext,{t=metaArgs}endlocalfunctioninext(t,i)-- This uses our __index metamethodlocalv=t[i+1]ifv~=nilthenreturni+1,vendendmetatable.__ipairs=function(t)-- Called when ipairs is run on the args table.returninext,t,0endreturnargsendreturnarguments