Module:DefInfo

From RimWorld Wiki
Revision as of 16:26, 16 April 2021 by Dr. Strangelove (talk | contribs) (beta, slightly leaning towards alpha (there will be insects))
Jump to navigation Jump to search

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