Module:Util

--- -- --                             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 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 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 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(' %s %s ', 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 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('', 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 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