模块:Arguments

来自萌娘共享
跳转至: 导航搜索

此模块的文档可以在模块:Arguments/doc创建

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