Mô đun:Arguments
Mô đun Lua này được sử dụng ở khoảng 1.890.000 trang, chiếm ≈ 10% tổng số trang. Để tránh gây lỗi trên quy mô lớn và tải máy chủ không cần thiết, tất cả thay đổi cần được thử nghiệm ở trang con /sandbox, /testcases của mô đun, hoặc ở chỗ thử mô đun. Các thay đổi đã được thử nghiệm có thể thêm vào mô đun bằng một sửa đổi duy nhất. Xin hãy thảo luận các thay đổi tại trang thảo luận trước khi áp dụng sửa đổi. |
Mô đun Lua này được sử dụng trong thông báo hệ thống, và ở khoảng 1.890.000 trang, chiếm ≈ 10% tổng số trang. Thay đổi đến nó có thể dẫn đến thay đổi ngay lập tức giao diện người dùng Wikipedia. Để tránh gây lỗi trên quy mô lớn và tải máy chủ không cần thiết, tất cả thay đổi cần được thử nghiệm ở trang con /sandbox, /testcases của mô đun, hoặc ở chỗ thử mô đun. Các thay đổi đã được thử nghiệm có thể thêm vào mô đun bằng một sửa đổi duy nhất. Xin hãy thảo luận các thay đổi tại trang thảo luận trước khi áp dụng sửa đổi. |
Mô-đun này là loại cần được khóa. Nó là một mô-đun nhìn thấy rõ được sử dụng bởi một số lượng lớn các trang, hoặc là các thay thế rất thường xuyên. Bởi vì phá hoại hoặc lỗi sai khi sửa sẽ ảnh hưởng đến nhiều trang và thậm chí chỉnh sửa tầm thường có thể gây ra tải đáng kể cho các máy chủ, nó được khóa bảo vệ hạn chế sửa đổi. |
Mô-đun này cung cấp xử lý dễ dàng các đối số được truyền từ #invoke. Đây là một mô-đun meta, được sử dụng cho các mô-đun khác và không được gọi trực tiếp từ #invoke. Các tính năng của nó bao gồm:
- Dễ dàng cắt xén các đối số và loại bỏ các đối số trống.
- Đối số có thể được truyền bởi cả khung hiện tại và khung cha cùng một lúc. (Thêm chi tiết bên dưới.)
- Các đối số có thể được truyền trực tiếp từ một mô-đun Lua khác hoặc từ bảng điều khiển gỡ lỗi.
- Đối số được tìm nạp khi cần, điều này có thể giúp tránh (một số) sự cố với thẻ
<ref>...</ref>
. - Hầu hết các tính năng có thể được tùy chỉnh.
Sử dụng cơ bản
Đầu tiên, bạn cần tải mô-đun. Nó chứa một hàm, được đặt tên getArgs
.
local getArgs = require('Module:Arguments').getArgs
Trong kịch bản cơ bản nhất, bạn có thể sử dụng getArgs bên trong chức năng chính của mình. Biến args
là bảng chứa các đối số từ #invoke. (Xem bên dưới để biết chi tiết.)
local getArgs = require('Module:Arguments').getArgs
local p = {}
function p.main(frame)
local args = getArgs(frame)
-- Main module code goes here.
end
return p
Tuy nhiên, thực tế nên đề xuất là sử dụng một hàm chỉ để xử lý các đối số từ #invoke. Điều này có nghĩa là nếu ai đó gọi mô-đun của bạn từ một mô-đun Lua khác, bạn không phải có sẵn một đối tượng khung, điều này giúp cải thiện hiệu suất.
local getArgs = require('Module:Arguments').getArgs
local p = {}
function p.main(frame)
local args = getArgs(frame)
return p._main(args)
end
function p._main(args)
-- Main module code goes here.
end
return p
Nếu bạn muốn nhiều hàm sử dụng các đối số và bạn cũng muốn chúng có thể truy cập được từ #invoke, bạn có thể sử dụng hàm bao bọc.
local getArgs = require('Module:Arguments').getArgs
local p = {}
local function makeInvokeFunc(funcName)
return function (frame)
local args = getArgs(frame)
return p[funcName](args)
end
end
p.func1 = makeInvokeFunc('_func1')
function p._func1(args)
-- Code for the first function goes here.
end
p.func2 = makeInvokeFunc('_func2')
function p._func2(args)
-- Code for the second function goes here.
end
return p
Tùy chỉnh
Một só Lựa chọn tùy chỉnh có sẵn khi sử dụng. Chúng được giải thích trong các phần dưới đây.
local args = getArgs(frame, {
trim = false,
removeBlanks = false,
valueFunc = function (key, value)
-- Code for processing one argument
end,
frameOnly = true,
parentOnly = true,
parentFirst = true,
wrappers = {
'Template:A wrapper template',
'Template:Another wrapper template'
},
readOnly = true,
noOverwrite = true
})
Cắt và loại bỏ khoảng trống
Các đối số trống thường gây khó đối với các lập trình viên mới muốn chuyển đổi các bản mẫu MediaWiki sang Lua. Trong cú pháp bản mẫu, chuỗi trống và chuỗi chỉ bao gồm khoảng trắng được coi là sai. Tuy nhiên, trong Lua, chuỗi trống và chuỗi bao gồm khoảng trắng được coi là đúng. Điều này có nghĩa là nếu bạn không chú ý đến những lập luận như vậy khi bạn viết các mô-đun Lua của mình, bạn có thể coi điều gì đó là đúng mà thực sự nên được coi là sai. Để tránh điều này, theo mặc định, mô-đun này sẽ xóa tất cả các đối số trống.
Tương tự, khoảng trắng có thể gây ra vấn đề khi xử lý các đối số vị trí. Mặc dù khoảng trắng được cắt bớt cho các đối số được đặt tên đến từ #invoke, nhưng nó được giữ nguyên cho các đối số vị trí. Hầu hết thời gian khoảng trắng bổ sung này là không mong muốn, vì vậy mô-đun này cắt nó theo mặc định.
Tuy nhiên, đôi khi bạn muốn sử dụng các đối số trống làm đầu vào và đôi khi bạn muốn giữ khoảng trắng bổ sung. Điều này có thể cần thiết để chuyển đổi một số mẫu chính xác như chúng đã được viết. Nếu bạn muốn làm điều này, bạn có thể đặt trim
và removeBlanks
với tham số là false
.
local args = getArgs(frame, {
trim = false,
removeBlanks = false
})
Định dạng tùy chỉnh của các đối số
Đôi khi bạn muốn xóa một số đối số trống nhưng không phải là đối số khác hoặc có lẽ bạn muốn đặt tất cả các đối số vị trí ở dạng chữ thường. Để làm những việc như thế này bạn có thể sử dụng tùy chọn valueFunc
. Đầu vào của tùy chọn này phải là một hàm có hai tham số, key
và value
, và trả về một giá trị duy nhất. Giá trị này là những gì bạn sẽ nhận được khi truy cập vào trường key
trong bảng args
.
Ví dụ 1: hàm này bảo toàn khoảng trắng cho đối số vị trí đầu tiên, nhưng cắt bỏ tất cả các đối số khác và loại bỏ tất cả các đối số trống khác.
local args = getArgs(frame, {
valueFunc = function (key, value)
if key == 1 then
return value
elseif value then
value = mw.text.trim(value)
if value ~= '' then
return value
end
end
return nil
end
})
Ví dụ 2: hàm này loại bỏ các đối số trống và chuyển đổi tất cả các đối số thành chữ thường, nhưng không cắt khoảng trắng khỏi các tham số vị trí.
local args = getArgs(frame, {
valueFunc = function (key, value)
if not value then
return nil
end
value = mw.ustring.lower(value)
if mw.ustring.find(value, '%S') then
return value
end
return nil
end
})
Lưu ý: các chức năng trên sẽ thất bại nếu đầu vào không thuộc loại string
hoặc nil
. Đây có thể là trường hợp nếu bạn sử dụng chức năng getArgs
trong chức năng chính của mô-đun của bạn và chức năng đó được gọi bởi một mô-đun Lua khác. Trong trường hợp này, bạn sẽ cần kiểm tra loại đầu vào của bạn. Đây không phải là vấn đề nếu bạn đang sử dụng một hàm đặc biệt cho các đối số từ #invoke (tức là bạn có chức năng p.main
và p._main
, hoặc một cái gì đó tương tự).
Ví dụ 1 và 2 với kiểm tra loại
|
---|
Ví dụ 1: local args = getArgs(frame, {
valueFunc = function (key, value)
if key == 1 then
return value
elseif type(value) == 'string' then
value = mw.text.trim(value)
if value ~= '' then
return value
else
return nil
end
else
return value
end
end
})
Ví dụ 2: local args = getArgs(frame, {
valueFunc = function (key, value)
if type(value) == 'string' then
value = mw.ustring.lower(value)
if mw.ustring.find(value, '%S') then
return value
else
return nil
end
else
return value
end
end
})
|
Ngoài ra, xin lưu ý rằng hàm valueFunc
được gọi nhiều hơn hoặc ít hơn mỗi khi một đối số được yêu cầu từ bảng args
, vì vậy nếu bạn quan tâm đến hiệu suất mà bạn nên chắc chắn rằng bạn không làm bất cứ điều gì không hiệu quả với mã của bạn.
Khung và khung cha
Đối số trong bảng args
có thể được truyền từ khung hiện tại hoặc từ khung chính của nó cùng một lúc. Để hiểu điều này có nghĩa là gì, dễ nhất là đưa ra một ví dụ. Hãy nói rằng chúng ta có một mô-đun gọi là Module:ExampleArgs
. Mô-đun này in hai đối số vị trí đầu tiên mà nó được thông qua.
Mã nguồn của Mô đun:ExampleArgs
|
---|
local getArgs = require('Module:Arguments').getArgs
local p = {}
function p.main(frame)
local args = getArgs(frame)
return p._main(args)
end
function p._main(args)
local first = args[1] or ''
local second = args[2] or ''
return first .. ' ' .. second
end
return p
|
Module:ExampleArgs
sau đó được gọi bởi Bản mẫu:ExampleArgs
, có chứa đoạn mã {{#invoke:ExampleArgs|main|firstInvokeArg}}
. Điều này tạo ra kết quả "firstInvokeArg".
Bây giờ nếu chúng ta gọi Bản mẫu:ExampleArgs
, điều sau đây sẽ xảy ra:
Mã | Kết quả |
---|---|
{{ExampleArgs}}
|
firstInvokeArg |
{{ExampleArgs|firstTemplateArg}}
|
firstInvokeArg |
{{ExampleArgs|firstTemplateArg|secondTemplateArg}}
|
firstInvokeArg secondTemplateArg |
Có ba tùy chọn bạn có thể đặt để thay đổi hành vi này:
frameOnly
, parentOnly
và parentFirst
. Nếu bạn đặt frameOnly
sau đó chỉ các đối số được truyền từ khung hiện tại sẽ được chấp nhận; nếu bạn đặt parentOnly
sau đó chỉ các đối số được truyền từ khung cha sẽ được chấp nhận; và nếu bạn đặt parentFirst
sau đó các đối số sẽ được truyền từ cả khung hiện tại và khung chính, nhưng khung chính sẽ có mức độ ưu tiên so với khung hiện tại. Dưới đây là kết quả với Bản mẫu:ExampleArgs
:
- frameOnly
Mã | Kết quả |
---|---|
{{ExampleArgs}}
|
firstInvokeArg |
{{ExampleArgs|firstTemplateArg}}
|
firstInvokeArg |
{{ExampleArgs|firstTemplateArg|secondTemplateArg}}
|
firstInvokeArg |
- parentOnly
Mã | Kết quả |
---|---|
{{ExampleArgs}}
|
|
{{ExampleArgs|firstTemplateArg}}
|
firstTemplateArg |
{{ExampleArgs|firstTemplateArg|secondTemplateArg}}
|
firstTemplateArg secondTemplateArg |
- parentFirst
Mã | Kết quả |
---|---|
{{ExampleArgs}}
|
firstInvokeArg |
{{ExampleArgs|firstTemplateArg}}
|
firstTemplateArg |
{{ExampleArgs|firstTemplateArg|secondTemplateArg}}
|
firstTemplateArg secondTemplateArg |
Chú ý:
- Nếu bạn đặt cả hai tùy chọn
frameOnly
vàparentOnly
, mô-đun sẽ không tìm nạp bất kỳ đối số nào từ #invoke. Đây có lẽ không phải là những gì bạn muốn. - Trong một số trường hợp, khung cha có thể không khả dụng, ví dụ: nếu getArgs thông qua khung cha chứ không phải khung hiện tại. Trong trường hợp này, chỉ các đối số khung sẽ được sử dụng (trừ khi parentOnly được đặt, trong trường hợp đó sẽ không có đối số nào được sử dụng) và tùy chọn
parentFirst
vàframeOnly
sẽ không có tác dụng.
Wrappers
Tùy chon wrappers được sử dụng để chỉ định một số lượng bản mẫu giới hạn như Bản mẫu wrapper, đó là, các bản mẫu có mục đích duy nhất là gọi một mô-đun. Nếu mô-đun phát hiện ra rằng nó đang được gọi từ một bản mẫu wrappers, nó sẽ chỉ kiểm tra các đối số trong khung cha; nếu không, nó sẽ chỉ kiểm tra các đối số trong khung được truyền cho getArgs. Điều này cho phép các mô-đun được gọi bằng #invoke hoặc thông qua mẫu wrappers mà không làm giảm hiệu suất liên quan đến việc phải kiểm tra cả khung và khung chính cho mỗi lần tra cứu đối số.
Ví dụ: nội dung duy nhất của Template:Side box (trừ nội dung trong cặp <noinclude>...</noinclude>
) là {{#invoke:Side box|main}}
. Không có điểm nào trong việc kiểm tra các đối số được truyền trực tiếp vào câu lệnh #invoke cho bản mẫu này, vì sẽ không có đối số nào được chỉ định ở đó. Chúng ta có thể tránh việc kiểm tra các đối số được truyền cho #invoke bằng cách sử dụng tùy chọn parentOnly, nhưng nếu chúng ta làm điều này thì #invoke cũng sẽ không hoạt động từ các trang khác. Nếu đây là trường hợp, |text=Some text
trong mã {{#invoke:Side box|main|text=Some text}}
sẽ bị bỏ qua hoàn toàn, bất kể nó được sử dụng từ trang nào. Bằng cách sử dụng tùy chọn wrappers
để cụ thể hóa 'Bản mẫu:Side box' là một wrapper, ta có thể tạo {{#invoke:Side box|main|text=Some text}}
hoạt động từ hầu hết các trang, trong khi vẫn không yêu cầu mô-đun kiểm tra các đối số trên chính trang Bản mẫu:Side box.
Wrappers có thể được chỉ định là một chuỗi hoặc là một chuỗi các chuỗi.
local args = getArgs(frame, {
wrappers = 'Template:Wrapper template'
})
local args = getArgs(frame, {
wrappers = {
'Template:Wrapper 1',
'Template:Wrapper 2',
-- Bất kì số nào của bản mẫu wrapper cũng có thể thêm vào đây.
}
})
Chú ý:
- Mô-đun sẽ tự động phát hiện nếu nó được gọi từ trang con /sandbox của bản mẫu wrapper, do đó không cần chỉ định rõ ràng các trang sandbox.
- Tùy chọn wrappers thay đổi hiệu quả mặc định của tùy chọn frameOnly và parentOnly. Nếu, ví dụ, parentOnly rõ ràng được đặt thành false với set wrappers, các cuộc gọi thông qua các bàn mẫu Wrapper sẽ dẫn đến cả hai đối số khung và đối số được tải, mặc dù các cuộc gọi không qua các mẫu trình bao bọc sẽ chỉ dẫn đến các đối số khung được tải.
- Nếu tùy chọn wrappers được đặt và không có khung cha nào khả dụng, mô-đun sẽ luôn nhận được các đối số từ khung được truyền cho
getArgs
.
Viết vào bảng args
Đôi khi nó có thể hữu ích để viết các giá trị mới vào bảng args. Điều này là có thể với các thiết lập mặc định của mô-đun này. (Tuy nhiên, hãy nhớ rằng thường là phong cách mã hóa tốt hơn để tạo một bảng mới với các giá trị mới của bạn và sao chép các đối số từ bảng args khi cần.)
args.foo = 'some value'
Có thể thay đổi hành vi này với tùy chọn readOnly
và noOverwrite
. Nếu readOnly
được đặt thì không thể ghi bất kỳ giá trị nào vào bảng args. Nếu noOverwrite
được đặt, sau đó có thể thêm các giá trị mới vào bảng, nhưng không thể thêm giá trị nếu nó ghi đè lên bất kỳ đối số nào được truyền từ #invoke.
Cập tags Ref
Mô đun này sử dụng metatables để tìm nạp các đối số từ #invoke. Điều này cho phép truy cập vào cả đối số khung và đối số khung cha mà không cần sử dụng hàm chức năng pairs()
. Điều này có thể giúp có ích nếu mô-đun của bạn có thể được thông qua tags <ref>...</ref>
làm đầu vào.
Ngay khi Tags <ref>...</ref>
được truy cập từ Lua, chúng được xử lý bởi phần mềm MediaWiki và tài liệu tham khảo sẽ xuất hiện trong danh sách tham khảo ở cuối bài viết. Nếu mô-đun của bạn tiến hành bỏ qua thẻ tham chiếu từ đầu ra, bạn sẽ kết thúc bằng một tham chiếu ảo - một tham chiếu xuất hiện trong danh sách tham chiếu, nhưng không có số nào liên kết với nó. Đây là một vấn đề với các mô-đun sử dụng pairs()
để phát hiện xem nên sử dụng các đối số từ khung hoặc khung cha, vì các mô-đun đó tự động xử lý mọi đối số có sẵn.
Mô-đun này giải quyết vấn đề này bằng cách cho phép truy cập vào cả đối số khung và khung cha, trong khi vẫn chỉ tìm nạp các đối số đó khi cần thiết. Vấn đề vẫn sẽ xảy ra nếu bạn sử dụng pairs(args)
ở nơi khác trong mô-đun của bạn, tuy nhiên.
Những hạn chế đã biết
Việc sử dụng các metatables cũng có nhược điểm của nó. Hầu hết các công cụ bảng Lua bình thường sẽ không hoạt động chính xác trên bảng args, bao gồm cả mã hoạt động #
, hàm chức năng next()
, và các chức năng trong thư viện bảng. Nếu việc sử dụng này là quan trọng cho mô-đun của bạn, bạn nên sử dụng chức năng xử lý đối số của riêng bạn thay vì mô-đun này.
-- 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.
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local arguments = {}
-- Generate four different tidyVal functions, so that we don't have to check the
-- options every time we call it.
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
local function tidyValTrimOnly(key, val)
if type(val) == 'string' then
return val:match('^%s*(.-)%s*$')
else
return val
end
end
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
local function tidyValNoChange(key, val)
return val
end
local function matchesTitle(given, title)
local tp = type( given )
return (tp == 'string' or tp == 'number') and mw.title.new( given ).prefixedText == title
end
local translate_mt = { __index = function(t, k) return k end }
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
if type(frame.args) == 'table' and type(frame.getParent) == 'function' then
if options.wrappers 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.
-- 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.
--]]
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
else
-- options.wrapper isn't set, so check the other options.
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(
"giá trị xấu được gán cho tùy chọn 'valueFunc'"
.. '(chức năng mong đợi, có '
.. 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)
local function mergeArgs(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 _, 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.
--]]
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.
--]]
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(
'không thể ghi vào khóa bảng đối số "'
.. tostring(key)
.. '"; bảng chỉ đọc',
2
)
elseif options.noOverwrite and args[key] ~= nil then
error(
'không thể ghi vào khóa bảng đối số "'
.. tostring(key)
.. '"; không được phép ghi đè các đối số hiện có',
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
metatable.__pairs = function ()
-- Called when pairs is run on the args table.
if not metatable.donePairs then
mergeArgs(argTables)
metatable.donePairs = true
end
return translatenext, { t = metaArgs }
end
local function inext(t, i)
-- This uses our __index metamethod
local v = t[i + 1]
if v ~= nil then
return i + 1, v
end
end
metatable.__ipairs = function (t)
-- Called when ipairs is run on the args table.
return inext, t, 0
end
return args
end
return arguments