[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: Difference between revisions
Jump to navigation
Jump to search
>Illviljan (Add pattern for a valid variable name) |
(Changed behavior of util.table.length. Now counts all elements in the table using pairs(). Old method was slow and not very useful, as it only counted numerically indexed elements anyway. This change is safe as far as I can tell, since this method it is not currently used in any modules that require Module:Util.) |
||
Line 1,176: | Line 1,176: | ||
util.table = {} | util.table = {} | ||
function util.table.length(tbl) | function util.table.length(tbl) | ||
-- Get | -- Get number of elements in a table. Counts both numerically indexed elements and associative elements. Does not count nil elements. | ||
for | local count = 0 | ||
for _ in pairs(tbl) do | |||
count = count + 1 | |||
end | end | ||
return count | |||
end | end | ||
util.table.count = util.table.length | |||
function util.table.assoc_to_array(tbl, args) | function util.table.assoc_to_array(tbl, args) |
Revision as of 04:18, 7 May 2021

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 number of elements in a table. Counts both numerically indexed elements and associative elements. Does not count nil elements.
local count = 0
for _ in pairs(tbl) do
count = count + 1
end
return count
end
util.table.count = util.table.length
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