This wiki is still in active development, many areas of the site are unstable or broken.
In the meantime, consider adding content to pages you like!
Module:BucketPut: Difference between revisions
From NBITTRPG Wiki
Created page with "local p = {} function p.main(frame) local args = frame:getParent().args local bucket_name = mw.text.trim(args[1] or '') local out_args = {} for k, v in pairs(args) do if v == '' then out_args[k] = nil else out_args[k] = v end end out_args[1] = nil bucket(bucket_name).put(out_args) end return p" |
mNo edit summary |
||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local getArgs = require('Module:Arguments').getArgs | |||
local yesno = require('Module:Yesno') | |||
local lang = mw.language.getContentLanguage() | |||
function p. | local O_TYPE = "type" | ||
local args = | local O_DELIM = "delimiter" | ||
local O_REQUIRED = "required" | |||
local OPTIONS = { O_TYPE, O_DELIM, O_REQUIRED } | |||
local O_ALIASES = { | |||
delim = 'delimiter', | |||
notnull = 'required', | |||
} | |||
local C_TARGS = "templateargs" | |||
local C_SUB = "sub" | |||
local C_NOSAVE = "nosave" | |||
local C_LOG = "log" | |||
local CONF_VALS = { C_TARGS, C_SUB, C_NOSAVE, C_LOG } | |||
---Is `value` one of the `targets`? | |||
---@param value string Type you want to check is in list. | |||
---@param targets string[] List of targets that `value` could be. | |||
---@return boolean | |||
local function oneof(value, targets) | |||
for _, target in ipairs(targets) do | |||
if value == target then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
---Parse a single value based on type name | |||
---@param t string Type name | |||
---@param v string Value to parse | |||
---@return boolean success Did parsing succeed? | |||
---@return any value Parsed value if success, string if fail. | |||
function p.parse_typed_value(t, v) | |||
-- probably should return an object instead | |||
if oneof(t, { "text", "page" }) then | |||
-- possible TODO with page | |||
return true, v | |||
end | |||
if oneof(t, { "int", "integer" }) then | |||
local val = tonumber(v) | |||
if val ~= nil and val % 1 == 0 then | |||
return true, val | |||
else | |||
return false, "integer" | |||
end | |||
end | |||
if oneof(t, { "float", "double", "num", "number" }) then | |||
local val = tonumber(v) | |||
if val ~= nil then | |||
return true, val | |||
else | |||
return false, "number" | |||
end | |||
end | |||
if oneof(t, { "bool", "boolean", "yesno" }) then | |||
local val = yesno(v) | |||
if val ~= nil then | |||
return true, val | |||
else | |||
return false, "boolean" | |||
end | |||
end | |||
if oneof(t, { "date", "time", "datetime", "timestamp" }) then | |||
local s, r = pcall(lang.formatDate, lang, "c", v) | |||
if s then | |||
return s, r | |||
else | |||
return false, "timestamp" | |||
end | |||
end | |||
if oneof(t, { "unixtime" }) then | |||
local s, r = pcall(lang.formatDate, lang, "U", v) | |||
if s then | |||
r = tonumber(r) | |||
or error("Could not convert " .. r .. " to a number") | |||
return s, r | |||
else | |||
return false, "Unix timestamp" | |||
end | |||
end | |||
if oneof(t, { "json" }) then | |||
local s, r = pcall(mw.text.jsonDecode, v) | |||
if s then | |||
return s, r | |||
else | |||
return false, "JSON" | |||
end | |||
end | |||
error("Unsupported type: " .. mw.dumpObject(t)) | |||
end | |||
---Recursively and safely add items of `keylist` to `args`. | |||
--- | |||
---`add_to_args({}, { "a", "b", "c" }, "d")` | |||
---will yield | |||
---`{ a = { b = { c = "d" } } }` | |||
---@param args table | |||
---@param keylist (string | number)[] | |||
---@param value any | |||
local function add_to_args(args, keylist, value) | |||
local nextvalue = args | |||
for i, valuename in ipairs(keylist) do | |||
if i == #keylist then | |||
nextvalue[valuename] = value | |||
else | |||
nextvalue[valuename] = nextvalue[valuename] or {} | |||
nextvalue = nextvalue[valuename] | |||
end | |||
end | |||
end | |||
---Parse specific key-value combination and modify output table. | |||
---@param out_args table | |||
---@param out_config table | |||
---@param key string | |||
---@param value string | |||
---@return nil | |||
function p.parse_args(out_args, out_config, key, value) | |||
if type(key) ~= "string" then | |||
return | |||
end | |||
value = mw.text.trim(value) | |||
if value == '' then | |||
return | |||
end | |||
-- basic conditions for parser working | |||
local optkey, optname = key:match("^([%w_]+)%-(.+)$") | |||
-- [%w_] matches all valid bucket field names at time of writing | |||
-- (non-exhaustively, i.e. all valid names are matched by [%w_], but [%w_] | |||
-- also matches some invalid names) | |||
if optkey and optname then | |||
if O_ALIASES[optname] then | |||
optname = O_ALIASES[optname] | |||
end | |||
if not oneof(optname, OPTIONS) then | |||
local fmt = "Invalid option %s for parameter %s" | |||
error(fmt:format(optname, optkey)) | |||
end | |||
return add_to_args(out_args, { optkey, optname }, value) | |||
end | |||
local ckey = key:match("^%+(.+)$") | |||
if ckey then | |||
if not oneof(ckey, CONF_VALS) then | |||
error(ckey .. " not a recognised config option") | |||
end | |||
out_config[ckey] = value | |||
-- modify out_config rather than out_args | |||
return | |||
end | |||
return add_to_args(out_args, { key, 1 }, value) | |||
-- none of the above apply so treat as raw arg | |||
end | |||
---Turn parsed argument object to single value to send to `mw.bucket.put`. | |||
---@param schema table | |||
---@param argname string | |||
---@param data table | |||
---@return any | |||
function p.get_bucket_arg(schema, argname, data) | |||
local item = data[1] | |||
if not item then | |||
if not yesno(data[O_REQUIRED]) then | |||
return | |||
end | |||
error(("Required argument %s was not given"):format(argname)) | |||
end | |||
item = mw.text.trim(item) | |||
local type = data[O_TYPE] or schema[argname].type or "TEXT" | |||
type = type:lower() | |||
if not data[O_DELIM] then | |||
local success, value_or_typename = p.parse_typed_value(type, item) | |||
if success then | |||
if value_or_typename == nil then | |||
error(("Trying to save %s as nil"):format(mw.dumpObject(argname))) | |||
end | |||
return value_or_typename | |||
-- value | |||
else | |||
local fmt = "Invalid %s given for parameter %s: %s" | |||
error(fmt:format(value_or_typename, mw.dumpObject(argname), mw.dumpObject(item))) | |||
-- typename | |||
end | |||
end | |||
local delim = data[O_DELIM] | |||
delim = delim:gsub('%f[\\]\\n', '\n') | |||
delim = delim:gsub('\\\\n', '\\n') | |||
--[[ | |||
removes a backslash from start, so: | |||
\n -> newline | |||
\\n -> \n | |||
\\\n -> \\n | |||
etc. | |||
]] | |||
local items = {} | |||
for entry in mw.text.gsplit(item, delim) do | |||
entry = mw.text.trim(entry) | |||
if entry ~= "" then | |||
local success, value_or_typename = p.parse_typed_value(type, entry) | |||
if success then | |||
table.insert(items, value_or_typename) | |||
-- value | |||
else | |||
local fmt = "Invalid %s given for parameter %s: %s" | |||
error(fmt:format(value_or_typename, mw.dumpObject(argname), mw.dumpObject(entry))) | |||
-- typename | |||
end | |||
end | |||
end | |||
return items | |||
end | |||
---Turn list of parsed arguments into `mw.bucket.put` arg table. | |||
---@param schema table | |||
---@param out_args table | |||
---@return table | |||
function p.get_bucket_args(schema, out_args) | |||
local bucket_args = {} | |||
for arg, data in pairs(out_args) do | |||
bucket_args[arg] = p.get_bucket_arg(schema, arg, data) | |||
end | |||
return bucket_args | |||
end | |||
---Main invokable function. | |||
---@param frame any | |||
function p.put(frame) | |||
local args = getArgs(frame, { frameOnly = true }) | |||
local parent_args = getArgs(frame, { parentOnly = true }) | |||
local bucket_name = mw.text.trim(args[1] or '') | local bucket_name = mw.text.trim(args[1] or '') | ||
local schema = mw.loadJsonData('bucket:' .. bucket_name) | |||
-- Replicate the behavior of $wgCargoStoreUseTemplateArgsFallback for specified arguments | |||
local implicit_store_args = mw.text.trim(args['+' .. C_TARGS] or '') | |||
for arg in implicit_store_args:gmatch("[^,]+") do | |||
arg = mw.text.trim(arg) | |||
args[arg] = parent_args[arg] | |||
end | |||
-- Convert all arguments to consistent format | |||
local out_args = {} | local out_args = {} | ||
local out_config = {} | |||
for k, v in pairs(args) do | for k, v in pairs(args) do | ||
p.parse_args(out_args, out_config, k, v) | |||
end | |||
local bucket_args = p.get_bucket_args(schema, out_args) | |||
if yesno(out_config[C_LOG]) then | |||
mw.log("Passed args: " .. mw.dumpObject(args)) | |||
mw.log("out_args: " .. mw.dumpObject(out_args)) | |||
mw.log("out_config: " .. mw.dumpObject(out_config)) | |||
mw.log("bucket_args: " .. mw.dumpObject(bucket_args)) | |||
end | |||
-- put changes to bucket | |||
if not yesno(out_config[C_NOSAVE]) then | |||
local bucket = mw.bucket(bucket_name) | |||
if out_config[C_SUB] then | |||
bucket.sub(out_config[C_SUB]) | |||
end | end | ||
bucket.put(bucket_args) | |||
end | end | ||
end | end | ||
return p | return p | ||
Latest revision as of 03:15, 4 October 2026
Documentation for this module may be created at Module:BucketPut/doc
local p = {}
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local lang = mw.language.getContentLanguage()
local O_TYPE = "type"
local O_DELIM = "delimiter"
local O_REQUIRED = "required"
local OPTIONS = { O_TYPE, O_DELIM, O_REQUIRED }
local O_ALIASES = {
delim = 'delimiter',
notnull = 'required',
}
local C_TARGS = "templateargs"
local C_SUB = "sub"
local C_NOSAVE = "nosave"
local C_LOG = "log"
local CONF_VALS = { C_TARGS, C_SUB, C_NOSAVE, C_LOG }
---Is `value` one of the `targets`?
---@param value string Type you want to check is in list.
---@param targets string[] List of targets that `value` could be.
---@return boolean
local function oneof(value, targets)
for _, target in ipairs(targets) do
if value == target then
return true
end
end
return false
end
---Parse a single value based on type name
---@param t string Type name
---@param v string Value to parse
---@return boolean success Did parsing succeed?
---@return any value Parsed value if success, string if fail.
function p.parse_typed_value(t, v)
-- probably should return an object instead
if oneof(t, { "text", "page" }) then
-- possible TODO with page
return true, v
end
if oneof(t, { "int", "integer" }) then
local val = tonumber(v)
if val ~= nil and val % 1 == 0 then
return true, val
else
return false, "integer"
end
end
if oneof(t, { "float", "double", "num", "number" }) then
local val = tonumber(v)
if val ~= nil then
return true, val
else
return false, "number"
end
end
if oneof(t, { "bool", "boolean", "yesno" }) then
local val = yesno(v)
if val ~= nil then
return true, val
else
return false, "boolean"
end
end
if oneof(t, { "date", "time", "datetime", "timestamp" }) then
local s, r = pcall(lang.formatDate, lang, "c", v)
if s then
return s, r
else
return false, "timestamp"
end
end
if oneof(t, { "unixtime" }) then
local s, r = pcall(lang.formatDate, lang, "U", v)
if s then
r = tonumber(r)
or error("Could not convert " .. r .. " to a number")
return s, r
else
return false, "Unix timestamp"
end
end
if oneof(t, { "json" }) then
local s, r = pcall(mw.text.jsonDecode, v)
if s then
return s, r
else
return false, "JSON"
end
end
error("Unsupported type: " .. mw.dumpObject(t))
end
---Recursively and safely add items of `keylist` to `args`.
---
---`add_to_args({}, { "a", "b", "c" }, "d")`
---will yield
---`{ a = { b = { c = "d" } } }`
---@param args table
---@param keylist (string | number)[]
---@param value any
local function add_to_args(args, keylist, value)
local nextvalue = args
for i, valuename in ipairs(keylist) do
if i == #keylist then
nextvalue[valuename] = value
else
nextvalue[valuename] = nextvalue[valuename] or {}
nextvalue = nextvalue[valuename]
end
end
end
---Parse specific key-value combination and modify output table.
---@param out_args table
---@param out_config table
---@param key string
---@param value string
---@return nil
function p.parse_args(out_args, out_config, key, value)
if type(key) ~= "string" then
return
end
value = mw.text.trim(value)
if value == '' then
return
end
-- basic conditions for parser working
local optkey, optname = key:match("^([%w_]+)%-(.+)$")
-- [%w_] matches all valid bucket field names at time of writing
-- (non-exhaustively, i.e. all valid names are matched by [%w_], but [%w_]
-- also matches some invalid names)
if optkey and optname then
if O_ALIASES[optname] then
optname = O_ALIASES[optname]
end
if not oneof(optname, OPTIONS) then
local fmt = "Invalid option %s for parameter %s"
error(fmt:format(optname, optkey))
end
return add_to_args(out_args, { optkey, optname }, value)
end
local ckey = key:match("^%+(.+)$")
if ckey then
if not oneof(ckey, CONF_VALS) then
error(ckey .. " not a recognised config option")
end
out_config[ckey] = value
-- modify out_config rather than out_args
return
end
return add_to_args(out_args, { key, 1 }, value)
-- none of the above apply so treat as raw arg
end
---Turn parsed argument object to single value to send to `mw.bucket.put`.
---@param schema table
---@param argname string
---@param data table
---@return any
function p.get_bucket_arg(schema, argname, data)
local item = data[1]
if not item then
if not yesno(data[O_REQUIRED]) then
return
end
error(("Required argument %s was not given"):format(argname))
end
item = mw.text.trim(item)
local type = data[O_TYPE] or schema[argname].type or "TEXT"
type = type:lower()
if not data[O_DELIM] then
local success, value_or_typename = p.parse_typed_value(type, item)
if success then
if value_or_typename == nil then
error(("Trying to save %s as nil"):format(mw.dumpObject(argname)))
end
return value_or_typename
-- value
else
local fmt = "Invalid %s given for parameter %s: %s"
error(fmt:format(value_or_typename, mw.dumpObject(argname), mw.dumpObject(item)))
-- typename
end
end
local delim = data[O_DELIM]
delim = delim:gsub('%f[\\]\\n', '\n')
delim = delim:gsub('\\\\n', '\\n')
--[[
removes a backslash from start, so:
\n -> newline
\\n -> \n
\\\n -> \\n
etc.
]]
local items = {}
for entry in mw.text.gsplit(item, delim) do
entry = mw.text.trim(entry)
if entry ~= "" then
local success, value_or_typename = p.parse_typed_value(type, entry)
if success then
table.insert(items, value_or_typename)
-- value
else
local fmt = "Invalid %s given for parameter %s: %s"
error(fmt:format(value_or_typename, mw.dumpObject(argname), mw.dumpObject(entry)))
-- typename
end
end
end
return items
end
---Turn list of parsed arguments into `mw.bucket.put` arg table.
---@param schema table
---@param out_args table
---@return table
function p.get_bucket_args(schema, out_args)
local bucket_args = {}
for arg, data in pairs(out_args) do
bucket_args[arg] = p.get_bucket_arg(schema, arg, data)
end
return bucket_args
end
---Main invokable function.
---@param frame any
function p.put(frame)
local args = getArgs(frame, { frameOnly = true })
local parent_args = getArgs(frame, { parentOnly = true })
local bucket_name = mw.text.trim(args[1] or '')
local schema = mw.loadJsonData('bucket:' .. bucket_name)
-- Replicate the behavior of $wgCargoStoreUseTemplateArgsFallback for specified arguments
local implicit_store_args = mw.text.trim(args['+' .. C_TARGS] or '')
for arg in implicit_store_args:gmatch("[^,]+") do
arg = mw.text.trim(arg)
args[arg] = parent_args[arg]
end
-- Convert all arguments to consistent format
local out_args = {}
local out_config = {}
for k, v in pairs(args) do
p.parse_args(out_args, out_config, k, v)
end
local bucket_args = p.get_bucket_args(schema, out_args)
if yesno(out_config[C_LOG]) then
mw.log("Passed args: " .. mw.dumpObject(args))
mw.log("out_args: " .. mw.dumpObject(out_args))
mw.log("out_config: " .. mw.dumpObject(out_config))
mw.log("bucket_args: " .. mw.dumpObject(bucket_args))
end
-- put changes to bucket
if not yesno(out_config[C_NOSAVE]) then
local bucket = mw.bucket(bucket_name)
if out_config[C_SUB] then
bucket.sub(out_config[C_SUB])
end
bucket.put(bucket_args)
end
end
return p
