Konigreich182@legacy41979339 (обсуждение | вклад) (Новая страница: «-- Utility stuff local util = {} util.cast = {} util.cast.bool_false = {'false', '0', 'disabled', 'off', 'no', ''} function util.cast.boolean(value) -- Take…») |
Нет описания правки |
||
(не показано 40 промежуточных версий 6 участников) | |||
Строка 1: | Строка 1: | ||
+ | ------------------------------------------------------------------------------- |
||
− | -- Utility stuff |
||
+ | -- |
||
+ | -- Module:Util |
||
+ | -- |
||
+ | -- This meta module contains a number of utility functions |
||
+ | ------------------------------------------------------------------------------- |
||
+ | |||
+ | local xtable = require('Module:Table') |
||
+ | local m_cargo -- Lazy load require('Module:Cargo') |
||
+ | |||
+ | -- The cfg table contains all localisable strings and configuration, to make it |
||
+ | -- easier to port this module to another wiki. |
||
+ | local cfg = mw.loadData('Module:Util/config') |
||
+ | |||
+ | local i18n = cfg.i18n |
||
local util = {} |
local util = {} |
||
+ | |||
+ | -- ---------------------------------------------------------------------------- |
||
+ | -- util.cast |
||
+ | -- ---------------------------------------------------------------------------- |
||
+ | |||
util.cast = {} |
util.cast = {} |
||
− | util.cast.bool_false = {'false', '0', 'disabled', 'off', 'no', ''} |
||
− | function util.cast. |
+ | function util.cast.text(value, args) |
− | -- Takes an |
+ | -- Takes an arbitary value and returns it as texts. |
-- |
-- |
||
+ | -- Also strips any categories |
||
− | -- for strings false will be according to util.cast.bool_false |
||
+ | -- |
||
+ | -- 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) |
local t = type(value) |
||
if t == 'nil' then |
if t == 'nil' then |
||
+ | if args.cast_nil == nil or args.cast_nil == true then |
||
− | return false |
||
+ | return false |
||
+ | else |
||
+ | return |
||
+ | end |
||
elseif t == 'boolean' then |
elseif t == 'boolean' then |
||
return value |
return value |
||
elseif t == 'number' then |
elseif t == 'number' then |
||
− | if |
+ | if value == 0 then return false end |
return true |
return true |
||
elseif t == 'string' then |
elseif t == 'string' then |
||
local tmp = string.lower(value) |
local tmp = string.lower(value) |
||
− | for _, v in ipairs( |
+ | for _, v in ipairs(i18n.bool_false) do |
if v == tmp then |
if v == tmp then |
||
return false |
return false |
||
Строка 26: | Строка 80: | ||
return true |
return true |
||
else |
else |
||
− | error( |
+ | error(string.format(i18n.errors.not_a_boolean, tostring(value), t)) |
end |
end |
||
+ | |||
− | |||
end |
end |
||
− | function util.cast.number(value, |
+ | function util.cast.number(value, args) |
-- Takes an abitary value and attempts to cast it to int |
-- Takes an abitary value and attempts to cast it to int |
||
-- |
-- |
||
+ | -- args: |
||
− | -- For strings: if default is nil and the conversion fails, an error will be returned |
||
+ | -- 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 t = type(value) |
||
− | + | local val |
|
+ | |||
if t == 'nil' then |
if t == 'nil' then |
||
− | + | val = nil |
|
elseif t == 'boolean' then |
elseif t == 'boolean' then |
||
if value then |
if value then |
||
− | + | val = 1 |
|
else |
else |
||
− | + | val = 0 |
|
end |
end |
||
elseif t == 'number' then |
elseif t == 'number' then |
||
− | + | val = value |
|
elseif t == 'string' then |
elseif t == 'string' then |
||
val = tonumber(value) |
val = tonumber(value) |
||
+ | end |
||
− | if val == nil then |
||
+ | |||
− | if default ~= nil then |
||
+ | if val == nil then |
||
− | return default |
||
+ | 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 |
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 |
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 |
else |
||
+ | error(string.format(i18n.errors.improper_stat, args.prefix, i)) |
||
− | return val |
||
end |
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 |
||
+ | |||
+ | m_cargo = m_cargo or 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 = m_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 |
||
+ | |||
+ | m_cargo = m_cargo or 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 |
else |
||
+ | m_cargo.store(frame, cargo_values) |
||
− | error('"' .. value .. ' is of uncaptured type "' .. t .. '"') |
||
end |
end |
||
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. Tip: Use Lua's square |
||
+ | bracket syntax for defining string literals. |
||
+ | |||
+ | 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(text, title, options) |
||
+ | -- Outputs html tag <abbr> as string or as mw.html node. |
||
+ | -- |
||
+ | -- options |
||
+ | -- class: class attribute |
||
+ | -- output: set to mw.html to return a mw.html node instead of a string |
||
+ | if not title then |
||
+ | return text |
||
+ | end |
||
+ | options = options or {} |
||
+ | local abbr = mw.html.create('abbr') |
||
+ | abbr:attr('title', title) |
||
+ | local class |
||
+ | if type(options) == 'table' and options.class then |
||
+ | class = options.class |
||
+ | else |
||
+ | class = options |
||
+ | end |
||
+ | if type(class) == 'string' then |
||
+ | abbr:attr('class', class) |
||
+ | end |
||
+ | abbr:wikitext(text) |
||
+ | if options.output == mw.html then |
||
+ | return abbr |
||
+ | end |
||
+ | return tostring(abbr) |
||
+ | 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('strong') |
||
+ | err |
||
+ | :addClass('error') |
||
+ | :tag('span') |
||
+ | :addClass('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(options) |
||
+ | -- |
||
+ | -- options: |
||
+ | -- as_tag |
||
+ | -- output: set to mw.html to return a mw.html node instead of a string |
||
+ | options = options 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(i18n.na) |
||
+ | :done() |
||
+ | if options.as_tag or options.output == mw.html then |
||
+ | return td |
||
+ | end |
||
+ | return tostring(td) |
||
+ | end |
||
+ | |||
+ | function util.html.format_value(tpl_args, frame, value, options) |
||
+ | -- value: table |
||
+ | -- min: |
||
+ | -- max: |
||
+ | -- options: table |
||
+ | -- func: Function to transform the value retrieved from the database |
||
+ | -- fmt: Format string (or function that returns format string) to use for the value. Default: '%s' |
||
+ | -- fmt_range: Format string to use for the value range. Default: '(%s-%s)' |
||
+ | -- color: poe_color code to use for the value range. False for no color. Default: 'mod' |
||
+ | -- class: Additional css class added to color tag |
||
+ | -- inline: Format string to use for the output |
||
+ | -- inline_color: poe_color code to use for the output. False for no color. Default: 'default' |
||
+ | -- inline_class: Additional css class added to inline color tag |
||
+ | -- no_color: (Deprecated; use color=false instead) |
||
+ | -- return_color: (Deprecated; returns both value.out and value without this) |
||
+ | if options.color ~= false and 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 then |
||
+ | value.min = options.func(tpl_args, frame, value.min) |
||
+ | value.max = options.func(tpl_args, frame, value.max) |
||
+ | end |
||
+ | options.fmt = options.fmt or '%s' |
||
+ | if type(options.fmt) == 'function' then -- Function that returns the format string |
||
+ | options.fmt = options.fmt(tpl_args, frame, value) |
||
+ | end |
||
+ | if value.min == value.max then -- Static value |
||
+ | value.out = string.format(options.fmt, value.min) |
||
+ | else -- Range value |
||
+ | options.fmt_range = options.fmt_range or i18n.range |
||
+ | value.out = string.format( |
||
+ | string.format(options.fmt_range, options.fmt, options.fmt), |
||
+ | value.min, |
||
+ | value.max |
||
+ | ) |
||
+ | end |
||
+ | if value.color then |
||
+ | value.out = util.html.poe_color(value.color, value.out, options.class) |
||
+ | end |
||
+ | if type(options.inline) == 'function' then |
||
+ | options.inline = options.inline(tpl_args, frame, value) |
||
+ | end |
||
+ | if options.inline and options.inline ~= '' then |
||
+ | value.out = string.format(options.inline, value.out) |
||
+ | if options.inline_color ~= false then |
||
+ | options.inline_color = options.inline_color or 'default' |
||
+ | value.out = util.html.poe_color(options.inline_color, value.out, options.inline_class) |
||
+ | end |
||
+ | end |
||
+ | local return_color = options.return_color and value.color or nil |
||
+ | if return_color then |
||
+ | return value.out, return_color |
||
+ | end |
||
+ | return value.out, value |
||
+ | 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 |
||
+ | |||
+ | function util.misc.maybe_sandbox(frame) |
||
+ | -- Did {{#invoke:}} call sandbox version? |
||
+ | frame = util.misc.get_frame(frame) |
||
+ | if string.find(frame:getTitle(), 'sandbox', 1, true) then |
||
+ | return true |
||
+ | end |
||
+ | return false |
||
+ | end |
||
+ | |||
+ | 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 cfg.misc.category_blacklist.sub_pages |
||
+ | local ns_blacklist = args.namespace_blacklist or cfg.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 |
return util |
Версия от 06:26, 11 июня 2021
Это мета-модуль, который предназначен для использования только другими модулями. Он не должен вызываться в викитексте. |
Этот модуль зависит от следующих других модулей: |
Описание
Предоставляет служебные функции для модулей программирования.
Структура
Группа | Описание |
---|---|
util.cast | утилиты для приведения значений (т. е. из аргументов) |
util.html | простые функции для создания HTML-тегов |
util.misc | прочие функции |
Использование
Этот модуль должен быть загружен с помощью require()
.
Вышеприведенная документация извлекается из Модуль:Util/doc.
Редакторы могут экспериментировать в sandbox этого модуля и в testcases страницах.
Подстраницы этого модуля.
Редакторы могут экспериментировать в sandbox этого модуля и в testcases страницах.
Подстраницы этого модуля.
-------------------------------------------------------------------------------
--
-- Module:Util
--
-- This meta module contains a number of utility functions
-------------------------------------------------------------------------------
local xtable = require('Module:Table')
local m_cargo -- Lazy load require('Module:Cargo')
-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = mw.loadData('Module:Util/config')
local i18n = cfg.i18n
local util = {}
-- ----------------------------------------------------------------------------
-- 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
m_cargo = m_cargo or 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 = m_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
m_cargo = m_cargo or 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. Tip: Use Lua's square
bracket syntax for defining string literals.
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(text, title, options)
-- Outputs html tag <abbr> as string or as mw.html node.
--
-- options
-- class: class attribute
-- output: set to mw.html to return a mw.html node instead of a string
if not title then
return text
end
options = options or {}
local abbr = mw.html.create('abbr')
abbr:attr('title', title)
local class
if type(options) == 'table' and options.class then
class = options.class
else
class = options
end
if type(class) == 'string' then
abbr:attr('class', class)
end
abbr:wikitext(text)
if options.output == mw.html then
return abbr
end
return tostring(abbr)
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('strong')
err
:addClass('error')
:tag('span')
:addClass('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(options)
--
-- options:
-- as_tag
-- output: set to mw.html to return a mw.html node instead of a string
options = options 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(i18n.na)
:done()
if options.as_tag or options.output == mw.html then
return td
end
return tostring(td)
end
function util.html.format_value(tpl_args, frame, value, options)
-- value: table
-- min:
-- max:
-- options: table
-- func: Function to transform the value retrieved from the database
-- fmt: Format string (or function that returns format string) to use for the value. Default: '%s'
-- fmt_range: Format string to use for the value range. Default: '(%s-%s)'
-- color: poe_color code to use for the value range. False for no color. Default: 'mod'
-- class: Additional css class added to color tag
-- inline: Format string to use for the output
-- inline_color: poe_color code to use for the output. False for no color. Default: 'default'
-- inline_class: Additional css class added to inline color tag
-- no_color: (Deprecated; use color=false instead)
-- return_color: (Deprecated; returns both value.out and value without this)
if options.color ~= false and 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 then
value.min = options.func(tpl_args, frame, value.min)
value.max = options.func(tpl_args, frame, value.max)
end
options.fmt = options.fmt or '%s'
if type(options.fmt) == 'function' then -- Function that returns the format string
options.fmt = options.fmt(tpl_args, frame, value)
end
if value.min == value.max then -- Static value
value.out = string.format(options.fmt, value.min)
else -- Range value
options.fmt_range = options.fmt_range or i18n.range
value.out = string.format(
string.format(options.fmt_range, options.fmt, options.fmt),
value.min,
value.max
)
end
if value.color then
value.out = util.html.poe_color(value.color, value.out, options.class)
end
if type(options.inline) == 'function' then
options.inline = options.inline(tpl_args, frame, value)
end
if options.inline and options.inline ~= '' then
value.out = string.format(options.inline, value.out)
if options.inline_color ~= false then
options.inline_color = options.inline_color or 'default'
value.out = util.html.poe_color(options.inline_color, value.out, options.inline_class)
end
end
local return_color = options.return_color and value.color or nil
if return_color then
return value.out, return_color
end
return value.out, value
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
function util.misc.maybe_sandbox(frame)
-- Did {{#invoke:}} call sandbox version?
frame = util.misc.get_frame(frame)
if string.find(frame:getTitle(), 'sandbox', 1, true) then
return true
end
return false
end
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 cfg.misc.category_blacklist.sub_pages
local ns_blacklist = args.namespace_blacklist or cfg.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