Module:Test
This module is used for development.
Purpose
This module is used to query information from the uploaded and parsed game files.
Its main purpose is to populate the infoboxes.
Usage
A note on the order of named parameters. All of the parameters that look like ...=...
are called named parameters and their order is not important (this is true for all templates).
query
{{#invoke:Test|query|<def ID>[|...|][|tag|][|sibling=...]}}
The work-horse. Output varies based on use:
- If only the
<def ID>
parameter is set, it will show the whole Def in the log. - If simple values are queried it will return them.
- If lists are queried it will return nothing but call
{{#vardefine}}
on all the simple values within it. What got defined can be seen in the page's log.
Named parameters:
<def ID>
- This parameter identifies the Def so it is mandatory. It can take two forms, if both are defined then
defName
takes preference.
- This parameter identifies the Def so it is mandatory. It can take two forms, if both are defined then
defName=<defName>
<defName>
(case sensitive) should be replaced with the actual defName of a Def.
label=<label>
<label>
(case insensitive) should be replaced with the actual label of a Def.
[sibling=...]
(optional) (case sensitive)- Allows querying for something if we know its sibling's value (works only for values at the moment).
Anonymous parameters:
[|...|]
(optional) (case sensitive)- Anonymous paramaters before the last one (
[tag]
) are here to help uniquely identify it. If the[tag]
is already unique within a Def tree, then these additional parameters are not needed.
- Anonymous paramaters before the last one (
[|tag|]
(optional) (case sensitive)- The final anonymous parameter defines what is to be queried.
count
{{#invoke:Test|count|<def ID>[|...|][|tag|][|sibling=...]}}
Parameters are the same as for query
. It's basically a wrapped up query that behaves a bit differently.
The difference is in how it handles lists. If a list is queried, unlike query
, it will return the length of the list.
How-to
Take a look at a Def
{{#invoke:Test|query|label=desert}}
Lua error at line 10: attempt to call global 'loadfile' (a nil value).
Data is in the log.
Retrieve a simple value
{{#invoke:Test|query|defName=Caribou|description}}
Lua error at line 10: attempt to call global 'loadfile' (a nil value).
Dealing with lists
{{#invoke:Test|query|defName=Mech_Scyther|tools}}
Lua error at line 10: attempt to call global 'loadfile' (a nil value).
When a list is retrieved there will be no output but the log will contain a list of defined variables.
For convenience the list is reprinted here:
tools_1_linkedBodyPartsGroup = LeftBlade tools_1_cooldownTime = 2 tools_1_label = left blade tools_1_DPS = 10 tools_1_power = 20 tools_1_capacities_1 = Cut tools_1_capacities_2 = Stab tools_2_linkedBodyPartsGroup = RightBlade tools_2_cooldownTime = 2 tools_2_label = right blade tools_2_DPS = 10 tools_2_power = 20 tools_2_capacities_1 = Cut tools_2_capacities_2 = Stab tools_3_linkedBodyPartsGroup = HeadAttackTool tools_3_capacities_1 = Blunt tools_3_label = head tools_3_DPS = 4.5 tools_3_chanceFactor = 0.2 tools_3_power = 9 tools_3_cooldownTime = 2
All of the above can be accessed with the use of {{#var:...}}
.
{{#var:tools_1_DPS}}
DPS is not a normal member of this table but has been added with Lua. Let's call it a virtual field.
Retrieve something if a sibling is known
{{#invoke:Test|query|label=guinea pig|minAge|sibling=AnimalAdult}}
Lua error at line 10: attempt to call global 'loadfile' (a nil value).
----------------- -- environment -- ----------------- _DEV = true data = {} if _DEV then data["Biomes"] = loadfile("./data/BiomeDefs.lua")() data["Races"] = loadfile("./data/ThingDefs_Races.lua")() else print = mw.log data["Biomes"] = mw.loadData('Module:Test/data/biomes') data["Races"] = mw.loadData('Module:Test/data/races') end ------------------------ -- load dev libraries -- ------------------------ --ref: https://github.com/kikito/inspect.lua if _DEV then inspect = require './lib/inspect' end --~ local inspect = loadfile("./lib/inspect.lua")() --~ print(inspect["_VERSION"]) ------------- -- utility -- ------------- ---procedure local function hl() print(string.rep("-", 80)) end --ref: https://gist.github.com/ripter/4270799 local function tprint (tbl, indent) if not indent then indent = 0 end if type(tbl) ~= "table" then print(tbl) return 0 end for k, v in pairs(tbl) do formatting = string.rep(" ", indent) .. k .. ": " if type(v) == "table" then print(formatting) tprint(v, indent+1) elseif type(v) == 'boolean' then print(formatting .. tostring(v)) else print(formatting .. v) end end end local function shallowcopy(original_table) local orig_type = type(original_table) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in pairs(original_table) do copy[orig_key] = orig_value end else -- number, string, boolean, etc copy = original_table end return copy end --ref: https://gist.github.com/balaam/3122129 local function reverse_numeric_table(tbl) local reversed_table = {} local length = #tbl for i,v in ipairs(tbl) do reversed_table[length + 1 - i] = v end return reversed_table end ---------- -- find -- ---------- local dr = { ["state"] = { ["args"] = {}, ["quads"] = {} }, ["ancestors"] = {} } function dr.conductor(...) local query, tbl, tbl_key, max_depth = ... local f_name = "find" assert(query, string.format("bad argument #1 to '%s' (argument missing, search query)", f_name)) assert(tbl, string.format("bad argument #2 to '%s' (argument missing, table to search through)", f_name)) max_depth = max_depth or "infinite" dr.state.args.query = query dr.state.args.tbl = tostring(tbl) dr.state.args.tbl_key = tbl_key or "" dr.state.args.max_depth = max_depth return dr.main(query, tbl, tbl_key, max_depth, 0) end local find = dr.conductor function dr.ancestry(quad, quads, ancestors) quad = quad or {} for _,v in ipairs(quads) do if v.v == quad.parent then table.insert(ancestors, quad.parent_key) dr.ancestry(v, quads, ancestors) elseif not quad.parent_key then return nil end end end function dr.condition(query, key, value) --todo: optimize local condition = false -- the order if query.key and query.value then condition = key == query.key and value == query.value elseif query.key then condition = key == query.key elseif query.value then condition = value == query.value elseif query.keys_of_type then if type(query.keys_of_type) == "table" then for _,v in ipairs(query.keys_of_type) do if type(key) == v then condition = true break end end else -- assume it's a key search condition = type(key) == query.keys_of_type end elseif query.values_of_type then if type(query.values_of_type) == "table" then for _,v in ipairs(query.values_of_type) do if type(value) == v then condition = true break end end else condition = type(value) == query.keys_of_type end end return condition end function dr.main(query, tbl, tbl_key, max_depth, depth) if max_depth ~= "infinite" then if depth > max_depth then return nil end end local subtables = {} for k,v in pairs(tbl) do local quad = {} quad.k = k quad.v = v quad.parent = tbl quad.parent_key = tbl_key quad.depth = depth table.insert(dr.state.quads, quad) if dr.condition(query, k, v) then return quad elseif type(v) == "table" then table.insert(subtables, k) end end for _,k in ipairs(subtables) do local f = dr.main(query, tbl[k], k, max_depth, depth+1) if f then return f end end end local q1 = {["key"] = "Hare"} local q2 = {["key"] = "defName", ["value"] = "Hare"} local q = q2 local r = find(q, data) --~ tprint(#dr.quads) dr.ancestry(r, dr.state.quads, dr.ancestors) --~ tprint(dr.state.args) print(inspect(r)) hl() tprint(r) --~ tprint({["ancestors"] = dr.ancestors}) --~ tprint({result = r}) ------------- -- utility -- ------------- local function count(tbl, key_type) local length = 0; for k,v in pairs(tbl) do if key_type then if type(k) == key_type then length = length + 1 end else length = length + 1 end end return length end -- "delimiter" must be a single character or the removal of the final one won't work local function string_csv(simple_table, delimiter) local f_name = "string_csv" delimiter = tostring(delimiter) or "," assert(#delimiter == 1, string.format("bad argument #2 to '%s' (single character expected)", f_name)) local list = "" for k,v in pairs(simple_table) do list = tostring(list) .. v .. delimiter end list = string.sub(list, 1, -2) return list end ---procedure local function vardefine(var_name, var_value) local f_name = "vardefine" assert(var_name, string.format("bad argument #1 to '%s' (argument missing, name of variable to define)", f_name)) assert(var_name == "string", string.format("bad argument #1 to '%s' (string expected, got %s)", f_name, type(var_name))) assert(var_value, string.format("bad argument #2 to '%s' (argument missing, value to assign to variable)", f_name)) assert(var_value == "string" or var_value == "number", string.format("bad argument #2 to '%s' (string or number expected, got %s)", f_name, type(var_value))) frame:callParserFunction('#vardefine', var_name, var_value) end ---procedure local function overwrite_first_table_with_second(first_table, second_table, ignore_keys) ignore_keys = ignore_keys or {} for k,v in pairs(second_table) do local ignore = false for _, ignored_key in ipairs(ignore_keys) do if k == ignored_key then ignore = true end end if not ignore then if type(v) == "table" then if type(first_table[k]) == "table" then overwrite_first_table_with_second(first_table[k], v) else first_table[k] = {} overwrite_first_table_with_second(first_table[k], v) end else first_table[k] = v end end end end ---------------------- -- dataset specific -- ---------------------- ---hidden vars: data local function find_def_table(def) for k1,v1 in pairs(data) do if type(v1) == "table" then for k2,v2 in pairs(v1) do if k2 == def then return v2 end end end end end ---hidden vars: data local function search_parent_def_table(key, def_table) local ParentName = getParentName(def_table) if not ParentName then return nil end local parentdef_table = search_table_recursive(ParentName, data) if not parentdef_table then return nil end local found = search_table_recursive(key, parentdef_table) if found then return found else found = search_parent_def_table(key, parentdef_table) if found then return found end end end ---hidden vars: data local function merge_def_with_parents(def_table, def_category, ignore_keys) local parent_names = {} local parent_name = def_table["ParentName"] local parent_table = def_category_table[parent_name] while parent_name do table.insert(parent_names, parent_name) parent_name = parent_table["ParentName"] parent_table = def_category_table[parent_name] end local inheritance_chain = shallowcopy(reverse_numeric_table(parent_names)) table.insert(inheritance_chain, def) local merged = {} local chain_length = #inheritance_chain for i,v in ipairs(inheritance_chain) do overwrite_first_table_with_second(merged, data[def_category][inheritance_chain[i]], ignore_keys) end return merged end ---hidden vars: data function def_info(key, value) assert(type(key) == "string" and type(data) == "table") local category local table_reference local t = search(data, "recursive", key, value) return { ["category"] = t, ["table_reference"] = table_reference, ["defName"] = defName } end ------------ -- public -- ------------ local p = {} --~ local categories = {} --~ for k,_ in pairs(data) do --~ p[k] = function (frame) end --ref: https://www.lua.org/manual/5.1/manual.html#2.5.9 --~ end function p.query(frame) local f_name = "query" assert(frame.args["defName"] or frame.args["label"], string.format("bad argument to '%s' (missing named argument 'defName' or 'label', needed to identify a Def)", f_name)) local sign = {} if frame.args["defName"] then sign.key = "defName" sign.value = frame.args["defName"] elseif frame.args["label"] then sign.key = "label" sign.value = frame.args["label"] end local q = search(data, "recursive", sign.key, sign.value, nil) if q then --~ q = merge_def_with_parents(data[q.parent_key][sign.], {"ParentName", "Abstract"}) end tprint(q) assert(q, "Def not found") --~ local numbered_args_length = count(frame.args, "number") --~ if numbered_args_length == 0 then --~ tprint(res) --~ return "Table returned. Check the log." --~ else --~ local filtered = res --~ -- filtered is just a reference to res (not copied) so updates on filtered are updates on res --~ for i=1, numbered_args_length do --~ assert(frame.args[i], string.format("bad argument to '%s' (missing numeric argument #%i)", f_name, i)) --~ filtered = search_table_recursive(frame.args[i], filtered) --~ assert(filtered, string.format("'%s' not found in '%s'", frame.args[i], def_query_tag)) --~ if filtered then --~ if (type(filtered) == "string" or type(filtered) == "number" or type(filtered) == "boolean") and (i < numbered_args_length) then --~ error(string.format("too many numeric arguments to '%s' ('%s' already found in '%s')", f_name, frame.args[i], def_query_tag)) --~ end --~ else --~ error(string.format("'%s' not found in '%s'", v, def_query_tag)) --~ end --~ end --~ if type(filtered) == "table" then --~ tprint(filtered) --~ return "Table returned. Check the log." --~ else --~ return filtered --~ end --~ end end ------------------------------------------------------------------- -- simulate MediaWiki/Scribunto module invocation using 'frame' -- ------------------------------------------------------------------- local simframe = { ["args"] = {} } simframe.args["label"] = "fennec fox" simframe.args[1] = "points" simframe.args[2] = "4" simframe.args[3] = "1" frame = frame or simframe --~ local invoke = p.query(frame) --~ if type(invoke) == "table" then --~ tprint(invoke) --~ else --~ print(invoke) --~ end --~ print(type(invoke)) --~ search_table_recursive(3, invoke) --------- -- log -- --------- local clock = string.format("Module:DefInfo:os.clock(): %i ms", os.clock() * 1000) print("--" .. string.rep("-", #clock) .. "--") print("- " .. clock .. " -") print("--" .. string.rep("-", #clock) .. "--") return p ------------------ --[[ references -- ------------------ - http://lua-users.org/wiki/CopyTable - https://stackoverflow.com/questions/1283388/lua-how-to-merge-two-tables-overwriting-the-elements-which-are-in-both - https://stackoverflow.com/questions/9168058/how-to-dump-a-table-to-console Some table.*() functions in Scribunto do not work as they should when using mw.loadData(). - https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.loadData ------------------ -- references ]]-- ------------------