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

  1. -- This module provides easy processing of arguments passed to Scribunto from
  2. -- #invoke. It is intended for use by other Lua modules, and should not be
  3. -- called from #invoke directly.
  4.  
  5. local libraryUtil = require('libraryUtil')
  6. local checkType = libraryUtil.checkType
  7.  
  8. local arguments = {}
  9.  
  10. -- Generate four different tidyVal functions, so that we don't have to check the
  11. -- options every time we call it.
  12.  
  13. local function tidyValDefault(key, val)
  14. if type(val) == 'string' then
  15. val = val:match('^%s*(.-)%s*$')
  16. if val == '' then
  17. return nil
  18. else
  19. return val
  20. end
  21. else
  22. return val
  23. end
  24. end
  25.  
  26. local function tidyValTrimOnly(key, val)
  27. if type(val) == 'string' then
  28. return val:match('^%s*(.-)%s*$')
  29. else
  30. return val
  31. end
  32. end
  33.  
  34. local function tidyValRemoveBlanksOnly(key, val)
  35. if type(val) == 'string' then
  36. if val:find('%S') then
  37. return val
  38. else
  39. return nil
  40. end
  41. else
  42. return val
  43. end
  44. end
  45.  
  46. local function tidyValNoChange(key, val)
  47. return val
  48. end
  49.  
  50. local function matchesTitle(given, title)
  51. local tp = type( given )
  52. return (tp == 'string' or tp == 'number') and mw.title.new( given ).prefixedText == title
  53. end
  54.  
  55. local translate_mt = { __index = function(t, k) return k end }
  56.  
  57. function arguments.getArgs(frame, options)
  58. checkType('getArgs', 1, frame, 'table', true)
  59. checkType('getArgs', 2, options, 'table', true)
  60. frame = frame or {}
  61. options = options or {}
  62.  
  63. --[[
  64. -- Set up argument translation.
  65. --]]
  66. options.translate = options.translate or {}
  67. if getmetatable(options.translate) == nil then
  68. setmetatable(options.translate, translate_mt)
  69. end
  70. if options.backtranslate == nil then
  71. options.backtranslate = {}
  72. for k,v in pairs(options.translate) do
  73. options.backtranslate[v] = k
  74. end
  75. end
  76. if options.backtranslate and getmetatable(options.backtranslate) == nil then
  77. setmetatable(options.backtranslate, {
  78. __index = function(t, k)
  79. if options.translate[k] ~= k then
  80. return nil
  81. else
  82. return k
  83. end
  84. end
  85. })
  86. end
  87.  
  88. --[[
  89. -- Get the argument tables. If we were passed a valid frame object, get the
  90. -- frame arguments (fargs) and the parent frame arguments (pargs), depending
  91. -- on the options set and on the parent frame's availability. If we weren't
  92. -- passed a valid frame object, we are being called from another Lua module
  93. -- or from the debug console, so assume that we were passed a table of args
  94. -- directly, and assign it to a new variable (luaArgs).
  95. --]]
  96. local fargs, pargs, luaArgs
  97. if type(frame.args) == 'table' and type(frame.getParent) == 'function' then
  98. if options.wrappers then
  99. --[[
  100. -- The wrappers option makes Module:Arguments look up arguments in
  101. -- either the frame argument table or the parent argument table, but
  102. -- not both. This means that users can use either the #invoke syntax
  103. -- or a wrapper template without the loss of performance associated
  104. -- with looking arguments up in both the frame and the parent frame.
  105. -- Module:Arguments will look up arguments in the parent frame
  106. -- if it finds the parent frame's title in options.wrapper;
  107. -- otherwise it will look up arguments in the frame object passed
  108. -- to getArgs.
  109. --]]
  110. local parent = frame:getParent()
  111. if not parent then
  112. fargs = frame.args
  113. else
  114. local title = parent:getTitle():gsub('/sandbox$', '')
  115. local found = false
  116. if matchesTitle(options.wrappers, title) then
  117. found = true
  118. elseif type(options.wrappers) == 'table' then
  119. for _,v in pairs(options.wrappers) do
  120. if matchesTitle(v, title) then
  121. found = true
  122. break
  123. end
  124. end
  125. end
  126.  
  127. -- We test for false specifically here so that nil (the default) acts like true.
  128. if found or options.frameOnly == false then
  129. pargs = parent.args
  130. end
  131. if not found or options.parentOnly == false then
  132. fargs = frame.args
  133. end
  134. end
  135. else
  136. -- options.wrapper isn't set, so check the other options.
  137. if not options.parentOnly then
  138. fargs = frame.args
  139. end
  140. if not options.frameOnly then
  141. local parent = frame:getParent()
  142. pargs = parent and parent.args or nil
  143. end
  144. end
  145. if options.parentFirst then
  146. fargs, pargs = pargs, fargs
  147. end
  148. else
  149. luaArgs = frame
  150. end
  151.  
  152. -- Set the order of precedence of the argument tables. If the variables are
  153. -- nil, nothing will be added to the table, which is how we avoid clashes
  154. -- between the frame/parent args and the Lua args.
  155. local argTables = {fargs}
  156. argTables[#argTables + 1] = pargs
  157. argTables[#argTables + 1] = luaArgs
  158.  
  159. --[[
  160. -- Generate the tidyVal function. If it has been specified by the user, we
  161. -- use that; if not, we choose one of four functions depending on the
  162. -- options chosen. This is so that we don't have to call the options table
  163. -- every time the function is called.
  164. --]]
  165. local tidyVal = options.valueFunc
  166. if tidyVal then
  167. if type(tidyVal) ~= 'function' then
  168. error(
  169. "bad value assigned to option 'valueFunc'"
  170. .. '(function expected, got '
  171. .. type(tidyVal)
  172. .. ')',
  173. 2
  174. )
  175. end
  176. elseif options.trim ~= false then
  177. if options.removeBlanks ~= false then
  178. tidyVal = tidyValDefault
  179. else
  180. tidyVal = tidyValTrimOnly
  181. end
  182. else
  183. if options.removeBlanks ~= false then
  184. tidyVal = tidyValRemoveBlanksOnly
  185. else
  186. tidyVal = tidyValNoChange
  187. end
  188. end
  189.  
  190. --[[
  191. -- Set up the args, metaArgs and nilArgs tables. args will be the one
  192. -- accessed from functions, and metaArgs will hold the actual arguments. Nil
  193. -- arguments are memoized in nilArgs, and the metatable connects all of them
  194. -- together.
  195. --]]
  196. local args, metaArgs, nilArgs, metatable = {}, {}, {}, {}
  197. setmetatable(args, metatable)
  198.  
  199. local function mergeArgs(tables)
  200. --[[
  201. -- Accepts multiple tables as input and merges their keys and values
  202. -- into one table. If a value is already present it is not overwritten;
  203. -- tables listed earlier have precedence. We are also memoizing nil
  204. -- values, which can be overwritten if they are 's' (soft).
  205. --]]
  206. for _, t in ipairs(tables) do
  207. for key, val in pairs(t) do
  208. if metaArgs[key] == nil and nilArgs[key] ~= 'h' then
  209. local tidiedVal = tidyVal(key, val)
  210. if tidiedVal == nil then
  211. nilArgs[key] = 's'
  212. else
  213. metaArgs[key] = tidiedVal
  214. end
  215. end
  216. end
  217. end
  218. end
  219.  
  220. --[[
  221. -- Define metatable behaviour. Arguments are memoized in the metaArgs table,
  222. -- and are only fetched from the argument tables once. Fetching arguments
  223. -- from the argument tables is the most resource-intensive step in this
  224. -- module, so we try and avoid it where possible. For this reason, nil
  225. -- arguments are also memoized, in the nilArgs table. Also, we keep a record
  226. -- in the metatable of when pairs and ipairs have been called, so we do not
  227. -- run pairs and ipairs on the argument tables more than once. We also do
  228. -- not run ipairs on fargs and pargs if pairs has already been run, as all
  229. -- the arguments will already have been copied over.
  230. --]]
  231.  
  232. metatable.__index = function (t, key)
  233. --[[
  234. -- Fetches an argument when the args table is indexed. First we check
  235. -- to see if the value is memoized, and if not we try and fetch it from
  236. -- the argument tables. When we check memoization, we need to check
  237. -- metaArgs before nilArgs, as both can be non-nil at the same time.
  238. -- If the argument is not present in metaArgs, we also check whether
  239. -- pairs has been run yet. If pairs has already been run, we return nil.
  240. -- This is because all the arguments will have already been copied into
  241. -- metaArgs by the mergeArgs function, meaning that any other arguments
  242. -- must be nil.
  243. --]]
  244. if type(key) == 'string' then
  245. key = options.translate[key]
  246. end
  247. local val = metaArgs[key]
  248. if val ~= nil then
  249. return val
  250. elseif metatable.donePairs or nilArgs[key] then
  251. return nil
  252. end
  253. for _, argTable in ipairs(argTables) do
  254. local argTableVal = tidyVal(key, argTable[key])
  255. if argTableVal ~= nil then
  256. metaArgs[key] = argTableVal
  257. return argTableVal
  258. end
  259. end
  260. nilArgs[key] = 'h'
  261. return nil
  262. end
  263.  
  264. metatable.__newindex = function (t, key, val)
  265. -- This function is called when a module tries to add a new value to the
  266. -- args table, or tries to change an existing value.
  267. if type(key) == 'string' then
  268. key = options.translate[key]
  269. end
  270. if options.readOnly then
  271. error(
  272. 'could not write to argument table key "'
  273. .. tostring(key)
  274. .. '"; the table is read-only',
  275. 2
  276. )
  277. elseif options.noOverwrite and args[key] ~= nil then
  278. error(
  279. 'could not write to argument table key "'
  280. .. tostring(key)
  281. .. '"; overwriting existing arguments is not permitted',
  282. 2
  283. )
  284. elseif val == nil then
  285. --[[
  286. -- If the argument is to be overwritten with nil, we need to erase
  287. -- the value in metaArgs, so that __index, __pairs and __ipairs do
  288. -- not use a previous existing value, if present; and we also need
  289. -- to memoize the nil in nilArgs, so that the value isn't looked
  290. -- up in the argument tables if it is accessed again.
  291. --]]
  292. metaArgs[key] = nil
  293. nilArgs[key] = 'h'
  294. else
  295. metaArgs[key] = val
  296. end
  297. end
  298.  
  299. local function translatenext(invariant)
  300. local k, v = next(invariant.t, invariant.k)
  301. invariant.k = k
  302. if k == nil then
  303. return nil
  304. elseif type(k) ~= 'string' or not options.backtranslate then
  305. return k, v
  306. else
  307. local backtranslate = options.backtranslate[k]
  308. if backtranslate == nil then
  309. -- Skip this one. This is a tail call, so this won't cause stack overflow
  310. return translatenext(invariant)
  311. else
  312. return backtranslate, v
  313. end
  314. end
  315. end
  316.  
  317. metatable.__pairs = function ()
  318. -- Called when pairs is run on the args table.
  319. if not metatable.donePairs then
  320. mergeArgs(argTables)
  321. metatable.donePairs = true
  322. end
  323. return translatenext, { t = metaArgs }
  324. end
  325.  
  326. local function inext(t, i)
  327. -- This uses our __index metamethod
  328. local v = t[i + 1]
  329. if v ~= nil then
  330. return i + 1, v
  331. end
  332. end
  333.  
  334. metatable.__ipairs = function (t)
  335. -- Called when ipairs is run on the args table.
  336. return inext, t, 0
  337. end
  338.  
  339. return args
  340. end
  341.  
  342. return arguments