[dismiss]
The wiki is currently a work in progress. If you'd like to help out, please check the Community Portal and our getting started guide. Also, check out our sister project on poewiki.net.
Module:Util

This is a meta module.
This module is meant to be used only by other modules. It should not be invoked in wikitext.
Overview
Provides utility functions for programming modules.
Structure
Group | Description |
---|---|
util.cast | utilities for casting values (i.e. from arguments) |
util.html | shorthand functions for creating some html tags |
util.misc | miscellaneous functions |
Usage
This module should be loaded with require()
.
The above documentation is transcluded from Module:Util/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-- Utility stuff
local xtable = require('Module:Table')
local util = {}
local string_format = string.format
local string_gsub = string.gsub
local infinity = math.huge
local mw = mw
local cargo = mw.ext.cargo
local i18n = {
bool_false = {'false', '0', 'disabled', 'off', 'no', '', 'deactivated'},
range = '(%s-%s)',
args = {
-- util.args.stat
stat_infix = 'stat',
stat_id = 'id',
stat_min = 'min',
stat_max = 'max',
stat_value = 'value',
-- util.args.weight_list
spawn_weight_prefix = 'spawn_weight',
generation_weight_prefix = 'generation_weight',
},
errors = {
-- util.cast.factory.*
missing_element = 'Element "%s" not found',
-- util.cast.factory.percentage
invalid_argument = 'Argument "%s" is invalid. Please check the documentation for acceptable values.',
not_a_percentage = '%s must be a percentage (in range 0 to 100).',
-- util.cast.boolean
not_a_boolean = 'value "%s" of type "%s" is not a boolean',
-- util.cast.number
not_a_number = 'value "%s" of type "%s" is not an integer',
number_too_small = '"%i" is too small. Minimum: "%i"',
number_too_large = '"%i" is too large. Maximum: "%i"',
-- util.cast.version
malformed_version_string = 'Malformed version string "%s"',
non_number_version_component = '"%s" has an non-number component',
unrecognized_version_number = '"%s" is not a recognized version number',
-- util.args.stats
improper_stat = '%sstat%s is improperly set; id and either value or min/max must be specified.',
-- util.args.weight_list
invalid_weight = 'Both %s and %s must be specified',
-- util.args.version
too_many_versions = 'The number of results (%s) does not match the number version arguments (%s)',
-- util.args.from_cargo_map
missing_key_in_fields = 'Key "%s" not found in the fields mapping of table "%s"',
table_object_as_default = 'Warning: table object as default value on key "%s" in mapping of table "%s"',
missing_key_in_order = 'Fields mapping of table "%s" has the following extra keys that are not handled by order:\n%s',
handler_returned_nil = 'Handler for "%s.fields.%s" returned nil for argument "%s". Check whether the value is correct for the given field type "%s".',
argument_required = 'Argument "%s" is required',
-- util.html.error
module_error = 'Module Error: ',
-- util.misc.raise_error_or_return
invalid_raise_error_or_return_usage = 'Invalid usage of raise_error_or_return.',
-- util.smw.array_query
duplicate_ids_found = 'Found multiple pages for id property "%s" with value "%s": %s, %s',
missing_ids_found = 'No results were found for id property "%s" with the following values: %s',
-- util.string.split_args
number_of_arguments_too_large = 'Number of arguments near = is too large (%s).',
},
}
-- ----------------------------------------------------------------------------
-- util.cast
-- ----------------------------------------------------------------------------
util.cast = {}
function util.cast.text(value, args)
-- Takes an arbitary value and returns it as texts.
--
-- Also strips any categories
--
-- args:
-- cast_nil - Cast lua nil value to "nil" string
-- Default: false
-- discard_empty - if the string is empty, return nil rather then empty string
-- Default: true
args = args or {}
if args.discard_empty == nil then
args.discard_empty = true
end
if value == nil and not args.cast_nil then
return
end
value = tostring(value)
if value == '' and args.discard_empty then
return
end
value = string.gsub(value, '%[%[Category:[%w_ ]+%]%]', '')
return value
end
function util.cast.boolean(value, args)
-- Takes an abitary value and casts it to a bool value
--
-- for strings false will be according to i18n.bool_false
--
-- args:
-- cast_nil - if set to false, it will not cast nil values
args = args or {}
local t = type(value)
if t == 'nil' then
if args.cast_nil == nil or args.cast_nil == true then
return false
else
return
end
elseif t == 'boolean' then
return value
elseif t == 'number' then
if value == 0 then return false end
return true
elseif t == 'string' then
local tmp = string.lower(value)
for _, v in ipairs(i18n.bool_false) do
if v == tmp then
return false
end
end
return true
else
error(string.format(i18n.errors.not_a_boolean, tostring(value), t))
end
end
function util.cast.number(value, args)
-- Takes an abitary value and attempts to cast it to int
--
-- args:
-- default: for strings, if default is nil and the conversion fails, an error will be returned
-- min: error if <min
-- max: error if >max
if args == nil then
args = {}
end
local t = type(value)
local val
if t == 'nil' then
val = nil
elseif t == 'boolean' then
if value then
val = 1
else
val = 0
end
elseif t == 'number' then
val = value
elseif t == 'string' then
val = tonumber(value)
end
if val == nil then
if args.default ~= nil then
val = args.default
else
error(string.format(i18n.errors.not_a_number, tostring(value), t))
end
end
if args.min ~= nil and val < args.min then
error(string.format(i18n.errors.number_too_small, val, args.min))
end
if args.max ~= nil and val > args.max then
error(string.format(i18n.errors.number_too_large, val, args.max))
end
return val
end
function util.cast.version(value, args)
-- Takes a string value and returns as version number
-- If the version number is invalid an error is raised
--
-- args:
-- return_type: defaults to "table"
-- table - Returns the version number broken down into sub versions as a table
-- string - Returns the version number as string
--
if args == nil then
args = {}
end
local result
if args.return_type == 'table' or args.return_type == nil then
result = util.string.split(value, '%.')
if #result ~= 3 then
error(string.format(i18n.errors.malformed_version_string, value))
end
result[4] = string.match(result[3], '%a+')
result[3] = string.match(result[3], '%d+')
for i=1,3 do
local v = tonumber(result[i])
if v == nil then
error(string.format(i18n.errors.non_number_version_component, value))
end
result[i] = v
end
elseif args.return_type == 'string' then
result = string.match(value, '%d+%.%d+%.%d+%a*')
end
if result == nil then
error(string.format(i18n.errors.unrecognized_version_number, value))
end
return result
end
--
-- util.cast.factory
--
-- This section is used to generate new functions for common argument parsing tasks based on specific options
--
-- All functions return a function which accepts two arguments:
-- tpl_args - arguments from the template
-- frame - current frame object
--
-- All factory functions accept have two arguments on creation:
-- k - the key in the tpl_args to retrive the value from
-- args - any addtional arguments (see function for details)
util.cast.factory = {}
function util.cast.factory.array_table(k, args)
-- Arguments:
-- tbl - table to check against
-- errmsg - error message if no element was found; should accept 1 parameter
args = args or {}
return function (tpl_args, frame)
local elements
if tpl_args[k] ~= nil then
elements = util.string.split(tpl_args[k], ',%s*')
for _, element in ipairs(elements) do
local r = util.table.find_in_nested_array{value=element, tbl=args.tbl, key='full'}
if r == nil then
error(string.format(args.errmsg or i18n.errors.missing_element, element))
end
end
tpl_args[args.key_out or k] = xtable:new(elements)
end
end
end
function util.cast.factory.table(k, args)
args = args or {}
return function (tpl_args, frame)
args.value = tpl_args[k]
if args.value == nil then
return
end
local value = util.table.find_in_nested_array(args)
if value == nil then
error(string.format(args.errmsg or i18n.errors.missing_element, k))
end
tpl_args[args.key_out or k] = value
end
end
function util.cast.factory.assoc_table(k, args)
-- Arguments:
--
-- tbl
-- errmsg
-- key_out
return function (tpl_args, frame)
local elements
if tpl_args[k] ~= nil then
elements = util.string.split(tpl_args[k], ',%s*')
for _, element in ipairs(elements) do
if args.tbl[element] == nil then
error(util.html.error{msg=string.format(args.errmsg or i18n.errors.missing_element, element)})
end
end
tpl_args[args.key_out or k] = elements
end
end
end
function util.cast.factory.number(k, args)
args = args or {}
return function (tpl_args, frame)
tpl_args[args.key_out or k] = tonumber(tpl_args[k])
end
end
function util.cast.factory.boolean(k, args)
args = args or {}
return function(tpl_args, frame)
if tpl_args[k] ~= nil then
tpl_args[args.key_out or k] = util.cast.boolean(tpl_args[k])
end
end
end
function util.cast.factory.percentage(k, args)
args = args or {}
return function (tpl_args, frame)
local v = tonumber(tpl_args[k])
if v == nil then
return util.html.error{msg=string.format(i18n.errors.invalid_argument, k)}
end
if v < 0 or v > 100 then
return util.html.error{msg=string.format(i18n.errors.not_a_percentage, k)}
end
tpl_args[args.key_out or k] = v
end
end
-- ----------------------------------------------------------------------------
-- util.args
-- ----------------------------------------------------------------------------
util.args = {}
function util.args.stats(argtbl, args)
-- in any prefix spaces should be included
--
-- argtbl: argument table to work with
-- args:
-- prefix: prefix if any
-- frame: frame used to set subobjects; if not set dont set properties
-- property_prefix: property prefix if any
-- subobject_prefix: subobject prefix if any
-- properties: table of properties to add if any
args = args or {}
args.prefix = args.prefix or ''
local i = 0
local stats = {}
repeat
i = i + 1
local prefix = string.format('%s%s%s_%s', args.prefix, i18n.args.stat_infix, i, '%s')
local id = {
id = string.format(prefix, i18n.args.stat_id),
min = string.format(prefix, i18n.args.stat_min),
max = string.format(prefix, i18n.args.stat_max),
value = string.format(prefix, i18n.args.stat_value),
}
local value = {}
for key, args_key in pairs(id) do
value[key] = argtbl[args_key]
end
if value.id ~= nil and ((value.min ~= nil and value.max ~= nil and value.value == nil) or (value.min == nil and value.max == nil and value.value ~= nil)) then
if value.value then
value.value = util.cast.number(value.value)
argtbl[id.value] = value.value
else
value.min = util.cast.number(value.min)
argtbl[id.min] = value.min
value.max = util.cast.number(value.max)
argtbl[id.max] = value.max
-- Also set average value
value.avg = (value.min + value.max)/2
argtbl[string.format('%sstat%s_avg', args.prefix, i)] = value.avg
end
argtbl[string.format('%sstat%s', args.prefix, i)] = value
stats[#stats+1] = value
elseif util.table.has_all_value(value, {'id', 'min', 'max', 'value'}, nil) then
value = nil
-- all other cases should be improperly set value
else
error(string.format(i18n.errors.improper_stat, args.prefix, i))
end
until value == nil
argtbl[string.format('%sstats', args.prefix)] = stats
end
function util.args.spawn_weight_list(argtbl, args)
args = args or {}
args.input_argument = i18n.args.spawn_weight_prefix
args.output_argument = 'spawn_weights'
args.cargo_table = 'spawn_weights'
util.args.weight_list(argtbl, args)
end
function util.args.generation_weight_list(argtbl, args)
args = args or {}
args.input_argument = i18n.args.generation_weight_prefix
args.output_argument = 'generation_weights'
args.cargo_table = 'generation_weights'
util.args.weight_list(argtbl, args)
end
function util.args.weight_list(argtbl, args)
-- Parses a weighted pair of lists and sets properties
--
-- argtbl: argument table to work with
-- args:
-- output_argument - if set, set arguments to this value
-- frame - if set, automtically set subobjects
-- input_argument - input prefix for parsing the arguments from the argtbl
-- subobject_name - name of the subobject
local m_cargo = require('Module:Cargo')
args = args or {}
args.input_argument = args.input_argument or 'spawn_weight'
local i = 0
local id = nil
local value = nil
if args.output_argument then
argtbl[args.output_argument] = {}
end
repeat
i = i + 1
id = {
tag = string.format('%s%s_tag', args.input_argument, i),
value = string.format('%s%s_value', args.input_argument, i),
}
value = {
tag = argtbl[id.tag],
value = argtbl[id.value],
}
if value.tag ~= nil and value.value ~= nil then
if args.output_argument then
argtbl[args.output_argument][i] = value
end
if args.frame and args.cargo_table then
m_cargo.store(args.frame, {
_table = args.cargo_table,
ordinal = i,
tag = value.tag,
weight = util.cast.number(value.value, {min=0}),
})
end
elseif not (value.tag == nil and value.value == nil) then
error(string.format(i18n.errors.invalid_weight, id.tag, id.value))
end
until value.tag == nil
end
function util.args.version(argtbl, args)
-- in any prefix spaces should be included
--
-- argtbl: argument table to work with
-- args:
-- frame: frame for queries
-- set_properties: if defined, set properties on the page
-- variables: table of prefixes
args = args or {}
args.variables = args.variables or {
release = {},
removal = {},
}
local version_ids = {}
local version_keys = {}
for key, data in pairs(args.variables) do
local full_key = string.format('%s_version', key)
if argtbl[full_key] ~= nil then
local value = util.cast.version(argtbl[full_key], {return_type = 'string'})
argtbl[full_key] = value
data.value = value
if data.property ~= nil then
version_ids[#version_ids+1] = value
version_keys[value] = key
end
end
end
-- no need to do a query if nothing was fetched
if #version_ids > 0 then
if args.frame == nil then
error('Properties were set, but frame was not')
end
for i, id in ipairs(version_ids) do
version_ids[i] = string.format('Versions.version="%s"', id)
end
local results = cargo.query(
'Versions',
'release_date, version',
{
where=table.concat(version_ids, ' OR '),
}
)
if #results ~= #version_ids then
error(string.format(i18n.too_many_versions, #results, #version_ids))
end
for _, row in ipairs(results) do
local key = version_keys[row.version]
argtbl[string.format('%s_date', key)] = row.release_date
end
end
end
function util.args.from_cargo_map(args)
-- Maps the arguments from a cargo argument table (i.e. the ones used in m_cargo.declare_factory)
--
-- It will expect/handle the following fields:
-- map.order - REQUIRED - Array table for the order in which the arguments in map.fields will be parsed
-- map.table - REQUIRED - Table name (for storage)
-- map.fields[id].field - REQUIRED - Name of the field in cargo table
-- map.fields[id].type - REQUIRED - Type of the field in cargo table
-- map.fields[id].func - OPTIONAL - Function to handle the arguments. It will be passed tpl_args and frame.
-- The function should return the parsed value.
--
-- If no function is specified, default handling depending on the cargo field type will be used
-- map.fields[id].default - OPTIONAL - Default value if the value is not set or returned as nil
-- If default is a function, the function will be called with (tpl_args, frame) and expected to return a default value for the field.
-- map.fields[id].name - OPTIONAL - Name of the field in tpl_args if it differs from the id in map.fields. Used for i18n for example
-- map.fields[id].required - OPTIONAL - Whether a value for the field is required or whether it can be left empty
-- Note: With a default value the field will never be empty
-- map.fields[id].skip - OPTIONAL - Skip field if missing from order
--
--
-- Expects argument table.
-- REQUIRED:
-- tpl_args - arguments passed to template after preprecessing
-- frame - frame object
-- table_map - table mapping object
-- rtr - if set return cargo props instead of storing them
local m_cargo = require('Module:Cargo')
local tpl_args = args.tpl_args
local frame = args.frame
local map = args.table_map
local cargo_values = {_table = map.table}
-- for checking missing keys in order
local available_fields = {}
for key, field in pairs(map.fields) do
if field.skip == nil then
available_fields[key] = true
end
end
-- main loop
for _, key in ipairs(map.order) do
local field = map.fields[key]
if field == nil then
error(string.format(i18n.errors.missing_key_in_fields, key, map.table))
else
available_fields[key] = nil
end
-- key in argument mapping
local args_key
if field.name then
args_key = field.name
else
args_key = key
end
-- Retrieve value
local value
-- automatic handling only works if the field type is set
if field.type ~= nil then
value = tpl_args[args_key]
local cfield = m_cargo.parse_field{field=field.type}
local handler
if cfield.type == 'Integer' or cfield.type == 'Float' then
handler = tonumber
elseif cfield.type == 'Boolean' then
handler = function (value)
return util.cast.boolean(value, {cast_nil=false})
end
end
if cfield.list and value ~= nil then
-- ingore whitespace between separator and values
value = util.string.split(value, cfield.list .. '%s*')
if handler then
for index, v in ipairs(value) do
value[index] = handler(v)
if value[index] == nil then
error(string.format(i18n.errors.handler_returned_nil, map.table, args_key, v, field.type))
end
end
end
elseif handler and value ~= nil then
value = handler(value)
if value == nil then
error(string.format(i18n.errors.handler_returned_nil, map.table, args_key, tpl_args[args_key], field.type))
end
end
-- Don't need special handling: String, Text, Wikitext, Searchtext
-- Consider: Page, Date, Datetime, Coordinates, File, URL, Email
end
if field.func ~= nil then
value = field.func(tpl_args, frame, value)
end
-- Check defaults
if value == nil and field.default ~= nil then
if type(field.default) == 'function' then
value = field.default(tpl_args, frame)
elseif type(field.default) == 'table' then
mw.logObject(string.format(i18n.errors.table_object_as_default, key, map.table))
value = mw.clone(field.default)
else
value = field.default
end
end
-- Add value to arguments and cargo data
if value ~= nil then
-- key will be used here since the value will be used internally from here on in english
tpl_args[key] = value
if field.field ~= nil then
cargo_values[field.field] = value
end
elseif field.required == true then
error(string.format(i18n.errors.argument_required, args_key))
end
end
-- check for missing keys and return error if any are missing
local missing = {}
for key, _ in pairs(available_fields) do
missing[#missing+1] = key
end
if #missing > 0 then
error(string.format(i18n.errors.missing_key_in_order, map.table, table.concat(missing, '\n')))
end
-- finally store data in DB
if args.rtr ~= nil then
return cargo_values
else
m_cargo.store(frame, cargo_values)
end
end
function util.args.template_to_lua(str)
--[[
Convert templates to lua format. Simplifes debugging and creating
examples.
Parameters
----------
str : string
The entire template wrapped into string.
Returns
-------
out : table
out.template - Template name.
out.args - arguments in table format.
out.args_to_str - arguments in readable string format.
]]
local out = {}
-- Get the template name:
out.template = string.match(str, '{{(.-)%s*|')
-- Remove everything but the arguments:
str = string.gsub(str, '%s*{{.-|', '')
str = string.gsub(str, '%s*}}%s*', '')
-- Split up the arguments:
out.args = {}
for i, v in ipairs(util.string.split(str, '%s*|%s*')) do
local arg = util.string.split(v, '%s*=%s*')
out.args[arg[1]] = arg[2]
out.args[#out.args+1] = arg[1]
end
-- Concate for easy copy/pasting:
local tbl = {}
for i, v in ipairs(out.args) do
tbl[#tbl+1]= string.format("%s='%s'", v, out.args[v])
end
out.args_to_str = table.concat(tbl, ',\n')
return out
end
-- ----------------------------------------------------------------------------
-- util.html
-- ----------------------------------------------------------------------------
util.html = {}
function util.html.abbr(abbr, text, class)
return string.format('<abbr title="%s" class="%s">%s</abbr>', text or '', class or '', abbr or '')
end
function util.html.error(args)
-- Create an error message box
--
-- Args:
-- msg - message
if args == nil then
args = {}
end
local err = mw.html.create('span')
err
:attr('class', 'module-error')
:wikitext(i18n.errors.module_error .. (args.msg or ''))
:done()
return tostring(err)
end
function util.html.poe_color(label, text, class)
if text == nil or text == '' then
return nil
end
class = class and (' ' .. class) or ''
return tostring(mw.html.create('em')
:attr('class', 'tc -' .. label .. class)
:wikitext(text))
end
util.html.poe_colour = util.html.poe_color
function util.html.tooltip(abbr, text, class)
return string.format('<span class="hoverbox c-tooltip %s"><span class="hoverbox__activator c-tooltip__activator">%s</span><span class="hoverbox__display c-tooltip__display">%s</span></span>', class or '', abbr or '', text or '')
end
util.html.td = {}
function util.html.td.na(args)
--
-- Args:
-- as_tag
args = args or {}
-- N/A table row, requires mw.html.create instance to be passed
local td = mw.html.create('td')
td
:attr('class', 'table-na')
:wikitext('N/A')
:done()
if args.as_tag then
return td
else
return tostring(td)
end
end
function util.html.format_value(tpl_args, frame, value, options)
-- value: table
-- min:
-- max:
-- options: table
-- fmt: formatter to use for the value instead of valfmt
-- fmt_range: formatter to use for the range values. Default: (%s to %s)
-- inline: Use this format string to insert value
-- inline_color: colour to use for the inline value; false to disable colour
-- func: Function to adjust the value with before output
-- color: colour code for util.html.poe_color, overrides mod colour
-- no_color: set to true to ingore colour entirely
-- return_color: also return colour
-- class: add this class to colour span
if options.no_color == nil then
if options.color then
value.color = options.color
elseif value.base ~= value.min or value.base ~= value.max then
value.color = 'mod'
else
value.color = 'value'
end
end
if options.func ~= nil then
value.min = options.func(tpl_args, frame, value.min)
value.max = options.func(tpl_args, frame, value.max)
end
if options.fmt == nil then
options.fmt = '%s'
elseif type(options.fmt) == 'function' then
options.fmt = options.fmt(tpl_args, frame, value)
end
if value.min == value.max then
value.out = string.format(options.fmt, value.min)
else
value.out = string.format(string.format(options.fmt_range or i18n.range, options.fmt, options.fmt), value.min, value.max)
end
if options.no_color == nil then
value.out = util.html.poe_color(value.color, value.out, options.class)
end
local return_color
if options.return_color ~= nil then
return_color = value.color
end
local text = options.inline
if type(text) == 'string' then
elseif type(text) == 'function' then
text = text(tpl_args, frame, value)
else
text = nil
end
if text and text ~= '' then
local color
if options.inline_color == nil then
color = 'default'
elseif options.inline_color ~= false then
color = color.inline_color
end
if color ~= nil then
text = util.html.poe_color(color, text, options.class)
end
return string.format(text, value.out), return_color
end
-- If we didn't return before, return here
return value.out, return_color
end
-- ----------------------------------------------------------------------------
-- util.misc
-- ----------------------------------------------------------------------------
util.misc = {}
function util.misc.is_frame(frame)
-- the type of the frame is a table containing the functions, so check whether some of these exist
-- should be enough to avoid collisions.
return not(frame == nil or type(frame) ~= 'table' or (frame.argumentPairs == nil and frame.callParserFunction == nil))
end
function util.misc.get_frame(frame)
if util.misc.is_frame(frame) then
return frame
end
return mw.getCurrentFrame()
end
util.misc.category_blacklist = {}
util.misc.category_blacklist.sub_pages = {
doc = true,
sandbox = true,
sandbox2 = true,
testcases = true,
}
util.misc.category_blacklist.namespaces = {
Template = true,
Template_talk = true,
Module = true,
Module_talk = true,
User = true,
User_talk = true,
}
function util.misc.add_category(categories, args)
-- categories: table of categories
-- args: table of extra arguments
-- namespace: id of namespace to validate against
-- ingore_blacklist: set to non-nil to ingore the blacklist
-- sub_page_blacklist: blacklist of subpages to use (if empty, use default)
-- namespace_blacklist: blacklist of namespaces to use (if empty, use default)
if type(categories) == 'string' then
categories = {categories}
end
if args == nil then
args = {}
end
local title = mw.title.getCurrentTitle()
local sub_blacklist = args.sub_page_blacklist or util.misc.category_blacklist.sub_pages
local ns_blacklist = args.namespace_blacklist or util.misc.category_blacklist.namespaces
if args.namespace ~= nil and title.namespace ~= args.namespace then
return ''
end
if args.ingore_blacklist == nil and (sub_blacklist[title.subpageText] or ns_blacklist[title.subjectNsText]) then
return ''
end
local cats = {}
for i, cat in ipairs(categories) do
cats[i] = string.format('[[Category:%s]]', cat)
end
return table.concat(cats)
end
function util.misc.raise_error_or_return(args)
--
-- Arguments:
-- args: table of arguments to this function (must be set)
-- One required:
-- raise_required: Don't raise errors and return html errors instead unless raisae is set in arguments
-- no_raise_required: Don't return html errors and raise errors insetad unless no_raise is set in arguments
--
-- Optional:
-- msg: error message to raise or return, default: nil
-- args: argument directory to validate against (e.x. template args), default: {}
args.args = args.args or {}
args.msg = args.msg or ''
if args.raise_required ~= nil then
if args.args.raise ~= nil then
error(args.msg)
else
return util.html.error{msg=args.msg}
end
elseif args.no_raise_required ~= nil then
if args.args.no_raise ~= nil then
return util.html.error{msg=args.msg}
else
error(args.msg)
end
else
error(i18n.errors.invalid_raise_error_or_return_usage)
end
end
-- ----------------------------------------------------------------------------
-- util.smw
-- ----------------------------------------------------------------------------
util.smw = {}
util.smw.data = {}
util.smw.data.rejected_namespaces = xtable:new({'User'})
function util.smw.safeguard(args)
-- Used for safeguarding data entry so it doesn't get added on user space stuff
--
-- Args:
-- smw_ingore_safeguard - ingore safeguard and return true
if args == nil then
args = {}
end
if args.smw_ingore_safeguard then
return true
end
local namespace = mw.site.namespaces[mw.title.getCurrentTitle().namespace].name
if util.smw.data.rejected_namespaces:contains(namespace) then
return false
end
return true
end
-- ----------------------------------------------------------------------------
-- util.string
-- ----------------------------------------------------------------------------
util.string = {}
function util.string.strip(str, pattern)
pattern = pattern or '%s'
return string.gsub(str, "^" .. pattern .. "*(.-)" .. pattern .. "*$", "%1")
end
function util.string.split(str, pattern)
-- Splits string into a table
--
-- str: string to split
-- pattern: pattern to use for splitting
local out = {}
local i = 1
local split_start, split_end = string.find(str, pattern, i)
while split_start do
out[#out+1] = string.sub(str, i, split_start-1)
i = split_end+1
split_start, split_end = string.find(str, pattern, i)
end
out[#out+1] = string.sub(str, i)
return out
end
function util.string.split_outer(str, pattern, outer)
--[[
Split a string into a table according to the pattern, ignoring
matching patterns inside the outer patterns.
Parameters
----------
str : string
String to split.
pattern : string
Pattern to split on.
outer : table of strings where #outer = 2.
Table with 2 strings that defines the opening and closing patterns
to match, for example parantheses or brackets.
Returns
-------
out : table
table of split strings.
Examples
--------
-- Nesting at the end:
str = 'mods.id, CONCAT(mods.id, mods.name)'
mw.logObject(util.split_outer(str, ',%s*', {'%(', '%)'}))
table#1 {
"mods.id",
"CONCAT(mods.id, mods.name)",
}
-- Nesting in the middle:
str = 'mods.id, CONCAT(mods.id, mods.name), mods.required_level'
mw.logObject(util.split_outer(str, ',%s*', {'%(', '%)'}))
table#1 {
"mods.id",
"CONCAT(mods.id, mods.name)",
"mods.required_level",
}
]]
local out = {}
local nesting_level = 0
local i = 0
local pttrn = '(.-)' .. '(' .. pattern .. ')'
for v, sep in string.gmatch(str, pttrn) do
if nesting_level == 0 then
-- No nesting is occuring:
out[#out+1] = v
else
-- Nesting is occuring:
out[#out] = (out[math.max(#out, 1)] or '') .. v
end
-- Increase nesting level:
if string.find(v, outer[1]) then -- Multiple matches?
nesting_level = nesting_level + 1
end
if string.find(v, outer[2]) then
nesting_level = nesting_level - 1
end
-- Add back the separator if nesting is occuring:
if nesting_level ~= 0 then
out[#out] = out[#out] .. sep
end
-- Get the last index value:
i = i + #v + #sep
end
-- Complement with the last part of the string:
if nesting_level == 0 then
out[#out+1] = string.sub(str, math.max(i+1, 1))
else
out[#out] = out[#out] .. string.sub(str, math.max(i+1, 1))
-- TODO: Check if nesting level is zero?
end
return out
end
function util.string.split_args(str, args)
-- Splits arguments string into a table
--
-- str: String of arguments to split
-- args: table of extra arguments
-- sep: separator to use (default: ,)
-- kvsep: separator to use for key value pairs (default: =)
local out = {}
if args == nil then
args = {}
end
args.sep = args.sep or ','
args.kvsep = args.kvsep or '='
if str ~= nil then
local row
for _, str in ipairs(util.string.split(str, args.sep)) do
row = util.string.split(str, args.kvsep)
if #row == 1 then
out[#out+1] = row[1]
elseif #row == 2 then
out[row[1]] = row[2]
else
error(string.format(i18n.number_of_arguments_too_large, #row))
end
end
end
return out
end
function util.string.format(str, vars)
--[[
Allow string replacement using named arguments.
TODO:
* Support %d ?
* Support 0.2f ?
Parameters
----------
str : String to replace.
vars : Table of arguments.
Examples
--------
= util.string.format('{foo} is {bar}.', {foo='Dinner', bar='nice'})
References
----------
http://lua-users.org/wiki/StringInterpolation
]]
if not vars then
vars = str
str = vars[1]
end
return (string_gsub(str, "({([^}]+)})",
function(whole, i)
return vars[i] or whole
end))
end
util.string.pattern = {}
function util.string.pattern.valid_var_name()
--[[
Get a pattern for a valid variable name.
]]
return '%A?([%a_]+[%w_]*)[^%w_]?'
end
-- ----------------------------------------------------------------------------
-- util.table
-- ----------------------------------------------------------------------------
util.table = {}
function util.table.length(tbl)
-- Get length of a table when # doesn't work (usually when a table has a metatable)
for i = 1, infinity do
if tbl[i] == nil then
return i - 1
end
end
end
function util.table.assoc_to_array(tbl, args)
-- Turn associative array into an array, discarding the values
local out = {}
for key, _ in pairs(tbl) do
out[#out+1] = key
end
return out
end
function util.table.has_all_value(tbl, keys, value)
-- Whether all the table values with the specified keys are the specified value
for _, k in ipairs(keys or {}) do
if tbl[k] ~= value then
return false
end
end
return true
end
function util.table.has_one_value(tbl, keys, value)
-- Whether one of table values with the specified keys is the specified value
for _, k in ipairs(keys or {}) do
if tbl[k] == value then
return true
end
end
return false
end
function util.table.find_in_nested_array(args)
-- Iterates thoguh the given nested array and finds the given value
--
-- ex.
-- data = {
-- {a=5}, {a=6}}
-- find_nested_array{arg=6, tbl=data, key='a'} -> 6
-- find_nested_array(arg=10, tbl=data, key='a'} -> nil
-- -> returns "6"
--
-- args: Table containing:
-- value: value of the argument
-- tbl: table of valid options
-- key: key or table of key of in tbl
-- rtrkey: if key is table, return this key instead of the value instead
-- rtrvalue: default: true
local rtr
if type(args.key) == 'table' then
for _, item in ipairs(args.tbl) do
for _, k in ipairs(args.key) do
if item[k] == args.value then
rtr = item
break
end
end
end
elseif args.key == nil then
for _, item in ipairs(args.tbl) do
if item == args.value then
rtr = item
break
end
end
else
for _, item in ipairs(args.tbl) do
if item[args.key] == args.value then
rtr = item
break
end
end
end
if rtr == nil then
return rtr
end
if args.rtrkey ~= nil then
return rtr[args.rtrkey]
elseif args.rtrvalue or args.rtrvalue == nil then
return args.value
else
return rtr
end
end
-- ----------------------------------------------------------------------------
-- util.Struct
-- ----------------------------------------------------------------------------
util.Struct = function(map)
local this = {map = map}
-- sets a value to a field
function this:set(field, value)
if not field or not value then
error('One or more arguments are nils')
end
local _ = self.map[field]
if not _ then
error(string_format('Field "%s" doesn\'t exist', field))
end
if _.validate then
_.value = _.validate(value)
else
_.value = value
end
-- this happen if 'validate' returns nil
if _.required == true and _.value == nil then
error(string_format('Field "%s" is required but has been set to nil', field))
end
end
-- adds a new prop to a field
function this:set_prop(field, prop, value)
if not field or not prop or not value then
error('One or more arguments are nils')
end
local _ = self.map[field]
if not _ then
error(string_format('Field "%s" doesn\'t exist', field))
end
_[prop] = value
end
-- gets a value from a field
function this:get(field)
if not field then
error('Argument field is nil')
end
local _ = self.map[field]
if not _ then
error(string_format('Field "%s" doesn\'t exist', field))
end
return _.value
end
-- gets a value from a prop field
function this:get_prop(field, prop)
if not field or not prop then
error('One or more arguments are nils')
end
local _ = self.map[field]
if not _ then
error(string_format('Field "%s" doesn\'t exist', field))
end
return _[prop]
end
-- shows a value from a field
function this:show(field)
if not field then
error('Argument field is nil')
end
local _ = self.map[field]
if not _ then
error(string_format('Field "%s" doesn\'t exist', field))
end
if _.show then
return _.show(_)
else
return _.value
end
end
return this
end
-- ----------------------------------------------------------------------------
return util