Difference between revisions of "Module:DefInfo"

From RimWorld Wiki
Jump to navigation Jump to search
m
(beta, slightly leaning towards alpha (there will be insects))
Line 1: Line 1:
 +
----------------------------------------------
 +
-- deal with differences in the environment --
 +
----------------------------------------------
 +
 +
if mw then
 +
  ENV = "wiki"
 +
  log = mw.log
 +
 +
  util = require("Module:Test/lib/util")
 +
  search = require("Module:Test/lib/search")
 +
else
 +
  ENV = "dev"
 +
 +
  mw = {}
 +
  log = {}
 +
 +
  inspect = require './lib/inspect'
 +
  util = require("./lib/util")
 +
  search = require("./lib/search")
 +
 +
  function pinspect(tbl, title)
 +
    util.hl(title)
 +
    print(inspect(tbl))
 +
  end
 +
 +
  -- define used mw functions that don't exist in dev environment
 +
  mw.logObject = function(obj, prefix)
 +
    if prefix then
 +
      assert(type(prefix) == "string")
 +
      table.insert(log, prefix .. " = " .. inspect(obj))
 +
    else
 +
      table.insert(log, inspect(obj))
 +
    end
 +
  end
 +
 +
  mw.dumpObject = function(arg)
 +
    return inspect(arg)
 +
  end
 +
 +
  mw.log = function(arg)
 +
    table.insert(log, arg)
 +
  end
 +
end
 +
 
---------------
 
---------------
 
-- load data --
 
-- load data --
 
---------------
 
---------------
  
local Biomes = mw.loadData('Module:Test/data/biomes')
+
data = {}
--local Buildings = mw.loadData('Module:Test/data/buildings')
+
 
local Races = mw.loadData('Module:Test/data/races')
+
if ENV == "dev" then
 +
  data["Biomes"] = loadfile("./data/BiomeDefs.lua")()
 +
  data["Races"] = loadfile("./data/ThingDefs_Races.lua")()
 +
elseif ENV == "wiki" then
 +
  data["Biomes"] = mw.loadData('Module:Test/data/biomes')
 +
  data["Races"] = mw.loadData('Module:Test/data/races')
 +
end
 +
 
 +
------------------
 +
-- virtual keys --
 +
------------------
  
-----------------------
+
-- this could be implemented with metatable events
-- private functions --
+
-- they get added in get(id_pair)
-----------------------
 
  
local function find_key_in_table(key, table)
+
local virtual_keys = {
  for k, v in pairs(table) do
+
  ["Races"] = {
    if k == key then return v
+
    ["lives_in"] = function (race, biomes)
    elseif type(v) == "table" then
+
      local list = {}
       local found = find_key_in_table(key, v)
+
      for biome_key, biome in pairs(biomes) do
       if found then return found end
+
        for _,animal in ipairs(biome.wildAnimals) do
 +
          if race.defName == animal then
 +
            table.insert(list, biome_key)
 +
          end
 +
        end
 +
       end
 +
       return list
 
     end
 
     end
 +
  }
 +
}
 +
 +
-------------
 +
-- private --
 +
-------------
 +
 +
local function vardefine(name, 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
 +
 +
 +
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
 
end
 
end
  
  
local function parent_defName(def, category)
+
local function merge_def(base_def_table, def_category, ignore_keys)
   local parent_name = category[def]["ParentName"]
+
 
   if type(category[parent_name]) == "table" then
+
  local ancestors = {}
     return parent_name
+
   local parent_name = base_def_table["ParentName"]
 +
   local parent_table = data[def_category][parent_name]
 +
 
 +
  while parent_name do
 +
    table.insert(ancestors, parent_name)
 +
    parent_name = parent_table["ParentName"]
 +
     parent_table = data[def_category][parent_name]
 
   end
 
   end
 +
 +
  local inheritance_chain = util.shallowcopy(util.reverse_numeric_table(ancestors))
 +
  table.insert(inheritance_chain, base_def_table.defName)
 +
 +
  local merged = {}
 +
  for i,v in ipairs(inheritance_chain) do
 +
    util.overwrite_first_table_with_second(merged, data[def_category][inheritance_chain[i]], ignore_keys)
 +
  end
 +
 +
  return merged
 
end
 
end
  
  
local function find_in_parents(tag, def, category)
+
function get_def(defName)
  if not category[def] then
+
  local base_def_table
     return def .. " not found in category"
+
  local def_category
 +
 
 +
  for catK,_ in pairs(data) do
 +
    for defK,def in pairs(data[catK]) do
 +
      if defK == defName then
 +
        base_def_table = def
 +
        def_category = catK
 +
      end
 +
     end
 
   end
 
   end
  
   local parent_def = parent_defName(def, category)
+
  if not base_def_table then return nil end
   if not parent_def then
+
 
     return tag .." not found in parent defs"
+
   local def = merge_def(base_def_table, def_category, {"ParentName", "Abstract"})
 +
 
 +
  -- add virtual keys
 +
   if virtual_keys[def_category] then
 +
     def._virtual = {}
 +
    for k,func in pairs(virtual_keys[def_category]) do
 +
      def._virtual[k] = func(def, data.Biomes)
 +
    end
 
   end
 
   end
  
   local found = find_key_in_table(tag, category[parent_def])
+
--~   mw.logObject(def, "def")
   if found then return found
+
  return def
   else
+
end
     found = find_in_parents(tag, parent_def, category)
+
 
    if found then
+
------------
       return found
+
-- public --
 +
------------
 +
 
 +
local p = {}
 +
 
 +
 
 +
-- will expect frame.args[1] to be the label
 +
function p.getDefName(frame)
 +
   local defName
 +
   for catK,_ in pairs(data) do
 +
     for defK,def in pairs(data[catK]) do
 +
      if def["label"] then
 +
        if string.upper(def["label"]) == string.upper(frame.args[1]) then defName = defK end
 +
       end
 
     end
 
     end
 
   end
 
   end
 +
 +
  if not defName then
 +
    mw.logObject(frame.args, "frame.args")
 +
    mw.log(string.format("'%s' not found", frame.args[1]))
 +
  end
 +
 +
  return defName
 +
end
 +
 +
function p.count(frame)
 +
  local query = p.query(frame)
 +
  return #query
 
end
 
end
  
 +
-- one function to rule them all, and in the darkness bind them
 +
function p.query(frame)
 +
 +
  -- implement shitloads of checks for arguments and the log so we know what's going on
 +
  -- use them as a kind of usage guide (give as much info as possible)
 +
  -- if wrong arguments are passed to private functions they will cause errors (they better)
  
local function query(tag, def, category)
+
   if not frame.args[1] then
   if not category[def] then
+
     mw.logObject(frame.args, "frame.args")
     return def .. " not found in category"
+
    mw.log("missing argument #1 (defName)")
 +
    return nil
 
   end
 
   end
  
   local found = find_key_in_table(tag, category[def])
+
   local def = get_def(frame.args[1])
  if found then return found end
 
  
   found = find_in_parents(tag, def, category)
+
   if not def then
   if found then return found end
+
    mw.logObject(frame.args, "frame.args")
 +
    mw.log(string.format("bad argument #1 ('%s' not found)", frame.args[1]))
 +
    return nil
 +
   end
  
   return tag .. " not found"
+
   local prune = def
end
 
  
--------------------------------
+
  -- #frame.args won't work as expected, check the doc
-- publicly exposed functions --
+
  local arg_count = util.count(frame.args, "number")
--------------------------------
 
  
local p = {}
+
  -- look at all the beautiful ifs!
 +
  for i,arg in ipairs(frame.args) do
 +
    -- frame.args are always strings on MediaWiki so convert the numbers back to numbers
 +
    arg = tonumber(arg) or arg
  
function p.query(frame)
+
    -- do stuff for additional arguments
  local category = frame.args[1]
+
    if i > 1 then
  local def = frame.args[2]
 
  local tag = frame.args[3]
 
  local sublist_query = frame.args[4]
 
  
  if category == "Races" then category = Races
+
      -- special checks for the final argument
  elseif category == "Biomes" then category = Biomes
+
      if i == arg_count then
  else return "undefined category" end
 
  
  local queried = query(tag, def, category)
+
        -- sibling
 +
        if frame.args["sibling"] then
 +
          prune = search.conductor({nil, frame.args["sibling"]} , prune)
 +
          if not prune then
 +
            mw.logObject(frame.args, "frame.args")
 +
            mw.log(string.format("bad argument 'sibling' ('%s' not found in '%s')", frame.args["sibling"], frame.args[i-1]))
 +
            return nil
 +
          else
 +
            prune = prune.parent.table[arg]
 +
            if not prune then
 +
              mw.logObject(frame.args, "frame.args")
 +
              mw.log(string.format("bad argument #%i ('%s' is not a sibling of '%s')", i, arg, frame.args["sibling"]))
 +
            end
 +
          end
 +
        else
 +
          prune = search.conductor(arg, prune)
 +
          if not prune then
 +
            mw.logObject(frame.args, "frame.args")
 +
            mw.log(string.format("bad argument #%i ('%s' not found in '%s')", i, frame.args[i], frame.args[i-1]))
 +
            return nil
 +
          else
 +
            prune = prune.value
 +
          end
 +
        end
  
  if sublist_query == "count" then
+
      else
    if type(queried) == "table" then
+
        prune = search.conductor(arg, prune)
      local count = 0;
+
        if not prune then
      -- ugly, fix
+
          mw.logObject(frame.args, "frame.args")
      for i, v in ipairs(queried) do
+
          mw.log(string.format("bad argument #%i ('%s' not found in '%s')", i, frame.args[i], frame.args[i-1]))
         count = i
+
          return nil
 +
         else
 +
          prune = prune.value
 +
        end
 
       end
 
       end
      return count
+
 
    else
 
      return tag .. " is not a table (not countable)"
 
 
     end
 
     end
  elseif sublist_query then
+
 
    return queried[tonumber(sublist_query)]
 
 
   end
 
   end
  
   return queried
+
  if type(prune) == "table" then mw.logObject(prune) end
 +
   return prune
 +
end
 +
 
 +
function p.logObject(frame)
 +
  mw.logObject(get(frame))
 
end
 
end
  
-- os.clock() may be logging time that is not the time I want
+
function p.dumpObject(frame)
mw.log("Module:DefInfo:os.clock() " .. os.clock()*1000 .. " ms")
+
  return mw.dumpObject(get(frame))
 +
end
 +
 
 +
 
 +
local clock = string.format("os.clock(): %i ms", os.clock() * 1000)
 +
mw.log("--" .. string.rep("-", #clock) .. "--")
 +
mw.log("- " .. clock .. " -")
 +
mw.log("--" .. string.rep("-", #clock) .. "--")
 +
 
 +
----------------------------------------
 +
-- simulate wiki log while developing --
 +
----------------------------------------
 +
 
 +
if ENV == "dev" then
 +
  util.hl("log")
 +
  for _,v in ipairs(log) do
 +
    print(v)
 +
  end
 +
end
  
 
return p
 
return p

Revision as of 16:26, 16 April 2021

Dev version at Module:Test


----------------------------------------------
-- deal with differences in the environment --
----------------------------------------------

if mw then
  ENV = "wiki"
  log = mw.log

  util = require("Module:Test/lib/util")
  search = require("Module:Test/lib/search")
else
  ENV = "dev"

  mw = {}
  log = {}

  inspect = require './lib/inspect'
  util = require("./lib/util")
  search = require("./lib/search")

  function pinspect(tbl, title)
    util.hl(title)
    print(inspect(tbl))
  end

  -- define used mw functions that don't exist in dev environment
  mw.logObject = function(obj, prefix)
    if prefix then
      assert(type(prefix) == "string")
      table.insert(log, prefix .. " = " .. inspect(obj))
    else
      table.insert(log, inspect(obj))
    end
  end

  mw.dumpObject = function(arg)
    return inspect(arg)
  end

  mw.log = function(arg)
    table.insert(log, arg)
  end
end

---------------
-- load data --
---------------

data = {}

if ENV == "dev" then
  data["Biomes"] = loadfile("./data/BiomeDefs.lua")()
  data["Races"] = loadfile("./data/ThingDefs_Races.lua")()
elseif ENV == "wiki" then
  data["Biomes"] = mw.loadData('Module:Test/data/biomes')
  data["Races"] = mw.loadData('Module:Test/data/races')
end

------------------
-- virtual keys --
------------------

-- this could be implemented with metatable events
-- they get added in get(id_pair)

local virtual_keys = {
  ["Races"] = {
    ["lives_in"] = function (race, biomes)
      local list = {}
      for biome_key, biome in pairs(biomes) do
        for _,animal in ipairs(biome.wildAnimals) do
          if race.defName == animal then
            table.insert(list, biome_key)
          end
        end
      end
      return list
    end
  }
}

-------------
-- private --
-------------

local function vardefine(name, 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


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


local function merge_def(base_def_table, def_category, ignore_keys)

  local ancestors = {}
  local parent_name = base_def_table["ParentName"]
  local parent_table = data[def_category][parent_name]

  while parent_name do
    table.insert(ancestors, parent_name)
    parent_name = parent_table["ParentName"]
    parent_table = data[def_category][parent_name]
  end

  local inheritance_chain = util.shallowcopy(util.reverse_numeric_table(ancestors))
  table.insert(inheritance_chain, base_def_table.defName)

  local merged = {}
  for i,v in ipairs(inheritance_chain) do
    util.overwrite_first_table_with_second(merged, data[def_category][inheritance_chain[i]], ignore_keys)
  end

  return merged
end


function get_def(defName)
  local base_def_table
  local def_category

  for catK,_ in pairs(data) do
    for defK,def in pairs(data[catK]) do
      if defK == defName then
        base_def_table = def
        def_category = catK
      end
    end
  end

  if not base_def_table then return nil end

  local def = merge_def(base_def_table, def_category, {"ParentName", "Abstract"})

  -- add virtual keys
  if virtual_keys[def_category] then
    def._virtual = {}
    for k,func in pairs(virtual_keys[def_category]) do
      def._virtual[k] = func(def, data.Biomes)
    end
  end

--~   mw.logObject(def, "def")
  return def
end

------------
-- public --
------------

local p = {}


-- will expect frame.args[1] to be the label
function p.getDefName(frame)
  local defName
  for catK,_ in pairs(data) do
    for defK,def in pairs(data[catK]) do
      if def["label"] then
        if string.upper(def["label"]) == string.upper(frame.args[1]) then defName = defK end
      end
    end
  end

  if not defName then
    mw.logObject(frame.args, "frame.args")
    mw.log(string.format("'%s' not found", frame.args[1]))
  end

  return defName
end

function p.count(frame)
  local query = p.query(frame)
  return #query
end

-- one function to rule them all, and in the darkness bind them
function p.query(frame)

  -- implement shitloads of checks for arguments and the log so we know what's going on
  -- use them as a kind of usage guide (give as much info as possible)
  -- if wrong arguments are passed to private functions they will cause errors (they better)

  if not frame.args[1] then
    mw.logObject(frame.args, "frame.args")
    mw.log("missing argument #1 (defName)")
    return nil
  end

  local def = get_def(frame.args[1])

  if not def then
    mw.logObject(frame.args, "frame.args")
    mw.log(string.format("bad argument #1 ('%s' not found)", frame.args[1]))
    return nil
  end

  local prune = def

  -- #frame.args won't work as expected, check the doc
  local arg_count = util.count(frame.args, "number")

  -- look at all the beautiful ifs!
  for i,arg in ipairs(frame.args) do
    -- frame.args are always strings on MediaWiki so convert the numbers back to numbers
    arg = tonumber(arg) or arg

    -- do stuff for additional arguments
    if i > 1 then

      -- special checks for the final argument
      if i == arg_count then

        -- sibling
        if frame.args["sibling"] then
          prune = search.conductor({nil, frame.args["sibling"]} , prune)
          if not prune then
            mw.logObject(frame.args, "frame.args")
            mw.log(string.format("bad argument 'sibling' ('%s' not found in '%s')", frame.args["sibling"], frame.args[i-1]))
            return nil
          else
            prune = prune.parent.table[arg]
            if not prune then
              mw.logObject(frame.args, "frame.args")
              mw.log(string.format("bad argument #%i ('%s' is not a sibling of '%s')", i, arg, frame.args["sibling"]))
            end
          end
        else
          prune = search.conductor(arg, prune)
          if not prune then
            mw.logObject(frame.args, "frame.args")
            mw.log(string.format("bad argument #%i ('%s' not found in '%s')", i, frame.args[i], frame.args[i-1]))
            return nil
          else
            prune = prune.value
          end
        end

      else
        prune = search.conductor(arg, prune)
        if not prune then
          mw.logObject(frame.args, "frame.args")
          mw.log(string.format("bad argument #%i ('%s' not found in '%s')", i, frame.args[i], frame.args[i-1]))
          return nil
        else
          prune = prune.value
        end
      end

    end

  end

  if type(prune) == "table" then mw.logObject(prune) end
  return prune
end

function p.logObject(frame)
  mw.logObject(get(frame))
end

function p.dumpObject(frame)
  return mw.dumpObject(get(frame))
end


local clock = string.format("os.clock(): %i ms", os.clock() * 1000)
mw.log("--" .. string.rep("-", #clock) .. "--")
mw.log("- " .. clock .. " -")
mw.log("--" .. string.rep("-", #clock) .. "--")

----------------------------------------
-- simulate wiki log while developing --
----------------------------------------

if ENV == "dev" then
  util.hl("log")
  for _,v in ipairs(log) do
    print(v)
  end
end

return p