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