Module:Infobox: Difference between revisions

From Official Factorio Wiki
Jump to navigation Jump to search
(revert recipe separator change (for now), fix bug)
(Created infobox tabs with space age support, without expensive mode support)
Line 1: Line 1:
local p = {}
local p = {}
function p.base_tab(frame) -- no prefix for args. Used for vanilla tab or if no tabs
  local args = frame:getParent().args
  local ret = {}
  local sa = frame:expandTemplate{title = "Space age"} .. " "
  if not p._empty_arg(args["recipe"]) then
    ret[#ret+1] = p._vrow(frame, {"Recipe",    args["recipe"], p._crafting_parsing(frame, args["recipe"])})
    ret[#ret+1] = p._vrow(frame, {"Total raw", args["recipe"], p._crafting_raw(frame, p._arg_or(args["total-raw"], args["recipe"]))})
  end
  if not p._empty_arg(args["cost"]) then
    local cost = p._item_parsing(frame, args["cost"]) .. ((not p._empty_arg(args["cost-multiplier"])) and "✖ <big>" .. args["cost-multiplier"] .. "</big>" or "")
    ret[#ret+1] = p._vrow(frame, {"Cost", args["cost"], cost})
  end
  ret[#ret+1] = p._extra    (frame,                          args["extra1"])
 
  if not p._empty_arg(args["map-color"]) then
    local color = frame:expandTemplate{title = 'Color', args = {args["map-color"]}}
    ret[#ret+1] = p._row(frame, {"Map color", args["map-color"], color})
  end
  ret[#ret+1] = p._row_simple(frame, "Map icon",              args["map-icon"])
  ret[#ret+1] = p._row_simple(frame, "Added in",              args["added-in"])
  ret[#ret+1] = p._row_simple(frame, "Walking speed",        args["walking-speed"])
  ret[#ret+1] = p._row_simple(frame, "Storage size",          args["storage-size"])
  ret[#ret+1] = p._row_simple(frame, "Fluid storage volume",  args["fluid-storage-volume"])
  if not p._empty_arg(args["expected-resources"]) then
    local label = p._translate(frame, "Expected resources")
    ret[#ret+1] = string.format(
[[<tr class="border-top">
<td>
%s
</td>
<td style="width: 70%%;">
%s
</td>
</tr>]], label, args["expected-resources"])
  end
  ret[#ret+1] = p._row_simple(frame, "Health",                args["health"])
  ret[#ret+1] = p._row_unit  (frame, "Restores",              args["restores"],            "health")
  ret[#ret+1] = p._row_simple(frame, "Resistances",          args["resistance"])
  ret[#ret+1] = p._row_unit  (frame, "Lifespan",              args["lifespan"],            "seconds")
  ret[#ret+1] = p._row_simple(frame, "Inventory size bonus",  args["inventory-size-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Equipment grid size",  args["grid-size"])
  ret[#ret+1] = p._row_simple(frame, "Stack size",            args["stack-size"])
  ret[#ret+1] = p._row_simple(frame, "Range",                args["range"])
  ret[#ret+1] = p._row_simple(frame, "Shooting speed",        args["shooting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Damage",                args["damage"])
  ret[#ret+1] = p._row_simple(frame, "Damage bonus",          args["damage-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Cluster size",          args["cluster-size"])
  ret[#ret+1] = p._row_simple(frame, "Area of effect size",  args["area-of-effect-size"])
  ret[#ret+1] = p._row_simple(frame, "Durability",            args["durability"])
  ret[#ret+1] = p._row_simple(frame, "Magazine size",        args["magazine-size"])
  ret[#ret+1] = p._row_item  (frame, "Ammunition",            args["ammunition"])
  ret[#ret+1] = p._vrow_item (frame, "Used as ammunition by", args["used-as-ammo-by"])
  ret[#ret+1] = p._row_simple(frame, "Efficiency",            args["efficiency"])
  ret[#ret+1] = p._row_simple(frame, "Dimensions",            args["dimensions"])
  ret[#ret+1] = p._row_type  (frame, "Energy consumption",    args["energy"])
  ret[#ret+1] = p._row_type  (frame, "Drain",                args["drain"])
  ret[#ret+1] = p._row_type  (frame, "Robot recharge rate",  args["robot-recharge-rate"])
  ret[#ret+1] = p._row_type  (frame, "Internal buffer recharge rate", args["internal-buffer-recharge-rate"])
  ret[#ret+1] = p._vrow_item (frame, "Placed in",            args["equipped-in"])
  ret[#ret+1] = p._row_simple(frame, "Robot limit",          args["robot-limit"])
  ret[#ret+1] = p._row_simple(frame, "Repair speed",          args["repair-speed"])
  ret[#ret+1] = p._row_simple(frame, "Charging stations",    args["charging-stations"])
  ret[#ret+1] = p._row_simple(frame, "Belt speed",            args["belt-speed"])
  ret[#ret+1] = p._row_simple(frame, "Movement bonus",        args["movement-bonus"])
  ret[#ret+1] = p._row_type  (frame, "Energy capacity",      args["energy-capacity"])
  ret[#ret+1] = p._row_simple(frame, "Power input",          args["power-input"])
  ret[#ret+1] = p._row_simple(frame, "Power output",          args["power-output"])
  ret[#ret+1] = p._row_simple(frame, "Maximum temperature",  args["maximum-temperature"])
  ret[#ret+1] = p._row_simple(frame, "Fluid consumption",    args["fluid-consumption"])
  ret[#ret+1] = p._row_simple(frame, "Shield hitpoints",      args["shield"])
  ret[#ret+1] = p._row_type  (frame, "Energy per hitpoint",  args["energy-per-hitpoint"])
  ret[#ret+1] = p._row_simple(frame, "Maximum recharge speed",args["maximum-recharge-speed"])
  ret[#ret+1] = p._row_simple(frame, "Crafting speed",        args["crafting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Pumping speed",        args["pumping-speed"])
  ret[#ret+1] = p._row_simple(frame, "Mining time",          args["mining-time"])
  ret[#ret+1] = p._row_simple(frame, "Speed",                args["speed"])
  ret[#ret+1] = p._row_simple(frame, "Productivity",          args["productivity"])
  ret[#ret+1] = p._row_simple(frame, "Mining speed",          args["mining-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Mining area",          args["mining-area"],          "tiles")
  ret[#ret+1] = p._row_type  (frame, "Fuel value",            args["fuel-value"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle acceleration",  args["vehicle-acceleration"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle top speed",    args["vehicle-top-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Supply area",          args["supply-area"],          "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Wire reach",            args["wire-reach"],            "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Construction area",    args["construction-area"],    "tiles")
  ret[#ret+1] = p._row_simple(frame, "Pollution",            args["pollution"])
  ret[#ret+1] = p._row_unit  (frame, sa .. "Spoil time",      args["spoil-time"],            "minutes")
  ret[#ret+1] = p._row_simple(frame, "Weight",                args["weight"])
  ret[#ret+1] = p._row_unit  (frame, "Module slots",          args["modules"],              "slots")
  if not p._empty_arg(args["prototype-type"]) then
    local prototype_type = frame:expandTemplate{title = 'Prototype page', args = {args["prototype-type"]}}
    ret[#ret+1] = p._row(frame, {"Prototype type", args["prototype-type"], prototype_type})
  end
  ret[#ret+1] = p._row_simple(frame, "Internal name",        args["internal-name"])
  ret[#ret+1] = p._vrow_item (frame, "Accepted equipment",    args["equipment"])
  ret[#ret+1] = p._vrow_tech (frame, "Required technologies", args["required-technologies"])
  ret[#ret+1] = p._vrow_tech (frame, "Allows",                args["allows"])
  ret[#ret+1] = p._vrow_item (frame, "Effects",              args["effects"])
  ret[#ret+1] = p._vrow_tech (frame, "Boosting technologies", args["boosting-technologies"])
  ret[#ret+1] = p._vrow_item (frame, "Produced by",          args["producers"])
  ret[#ret+1] = p._vrow_item (frame, "Consumed by",          args["consumers"])
  ret[#ret+1] = p._vrow_item (frame, "Valid fuel",            args["valid-fuel"])
  ret[#ret+1] = p._vrow_item (frame, "Used as fuel by",      args["used-as-fuel-by"])
  ret[#ret+1] = p._vrow_item (frame, sa .. "Recyling results",args["recycling-results"])
  ret[#ret+1] = p._extra    (frame,                          args["extra2"])
  return table.concat(ret)
end
function p.space_age_tab(frame) -- space-age prefix for some args. Used for space age mod tab
  local args = frame:getParent().args
  local ret = {}
  local sa = frame:expandTemplate{title = "Space age"} .. " "
 
  local recipe = p._arg_or(args["space-age-recipe"], args["recipe"])
  if not p._empty_arg(recipe) then
    ret[#ret+1] = p._vrow(frame, {"Recipe",    recipe, p._crafting_parsing(frame, recipe)})
    local vanilla_total_raw = p._arg_or(args["total-raw"], args["recipe"])
    ret[#ret+1] = p._vrow(frame, {"Total raw", recipe, p._crafting_raw(frame, p._arg_or(args["space-age-total-raw"],  p._arg_or(args["space-age-recipe"], vanilla_total_raw)))})
  end
  local cost_arg = p._arg_or(args["space-age-cost"], args["cost"])
  if not p._empty_arg(cost_arg) then
    local cost_multiplier_arg = p._arg_or(args["space-age-cost-multiplier"], args["cost-multiplier"])
    local cost = p._item_parsing(frame, cost_arg) .. ((not p._empty_arg(cost_multiplier_arg)) and "✖ <big>" .. cost_multiplier_arg .. "</big>" or "")
    ret[#ret+1] = p._vrow(frame, {"Cost", cost_arg, cost})
  end
  ret[#ret+1] = p._extra    (frame,                          args["extra1"])
 
  if not p._empty_arg(args["map-color"]) then
    local color = frame:expandTemplate{title = 'Color', args = {args["map-color"]}}
    ret[#ret+1] = p._row(frame, {"Map color", args["map-color"], color})
  end
  ret[#ret+1] = p._row_simple(frame, "Map icon",              args["map-icon"])
  ret[#ret+1] = p._row_simple(frame, "Added in",              args["added-in"])
  ret[#ret+1] = p._row_simple(frame, "Walking speed",        args["walking-speed"])
  ret[#ret+1] = p._row_simple(frame, "Storage size",          args["storage-size"])
  ret[#ret+1] = p._row_simple(frame, "Fluid storage volume",  args["fluid-storage-volume"])
  if not p._empty_arg(args["expected-resources"]) then
    local label = p._translate(frame, "Expected resources")
    ret[#ret+1] = string.format(
[[<tr class="border-top">
<td>
%s
</td>
<td style="width: 70%%;">
%s
</td>
</tr>]], label, args["expected-resources"])
  end
  ret[#ret+1] = p._row_simple(frame, "Health",                args["health"])
  ret[#ret+1] = p._row_unit  (frame, "Restores",              args["restores"],            "health")
  ret[#ret+1] = p._row_simple(frame, "Resistances",          args["resistance"])
  ret[#ret+1] = p._row_unit  (frame, "Lifespan",              args["lifespan"],            "seconds")
  ret[#ret+1] = p._row_simple(frame, "Inventory size bonus",  args["inventory-size-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Equipment grid size",  args["grid-size"])
  ret[#ret+1] = p._row_simple(frame, "Stack size",            args["stack-size"])
  ret[#ret+1] = p._row_simple(frame, "Range",                args["range"])
  ret[#ret+1] = p._row_simple(frame, "Shooting speed",        args["shooting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Damage",                args["damage"])
  ret[#ret+1] = p._row_simple(frame, "Damage bonus",          args["damage-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Cluster size",          args["cluster-size"])
  ret[#ret+1] = p._row_simple(frame, "Area of effect size",  args["area-of-effect-size"])
  ret[#ret+1] = p._row_simple(frame, "Durability",            args["durability"])
  ret[#ret+1] = p._row_simple(frame, "Magazine size",        args["magazine-size"])
  ret[#ret+1] = p._row_item  (frame, "Ammunition",            args["ammunition"])
  ret[#ret+1] = p._vrow_item (frame, "Used as ammunition by", args["used-as-ammo-by"])
  ret[#ret+1] = p._row_simple(frame, "Efficiency",            args["efficiency"])
  ret[#ret+1] = p._row_simple(frame, "Dimensions",            args["dimensions"])
  ret[#ret+1] = p._row_type  (frame, "Energy consumption",    args["energy"])
  ret[#ret+1] = p._row_type  (frame, "Drain",                args["drain"])
  ret[#ret+1] = p._row_type  (frame, "Robot recharge rate",  args["robot-recharge-rate"])
  ret[#ret+1] = p._row_type  (frame, "Internal buffer recharge rate", args["internal-buffer-recharge-rate"])
  ret[#ret+1] = p._vrow_item (frame, "Placed in",            args["equipped-in"])
  ret[#ret+1] = p._row_simple(frame, "Robot limit",          args["robot-limit"])
  ret[#ret+1] = p._row_simple(frame, "Repair speed",          args["repair-speed"])
  ret[#ret+1] = p._row_simple(frame, "Charging stations",    args["charging-stations"])
  ret[#ret+1] = p._row_simple(frame, "Belt speed",            args["belt-speed"])
  ret[#ret+1] = p._row_simple(frame, "Movement bonus",        args["movement-bonus"])
  ret[#ret+1] = p._row_type  (frame, "Energy capacity",      args["energy-capacity"])
  ret[#ret+1] = p._row_simple(frame, "Power input",          args["power-input"])
  ret[#ret+1] = p._row_simple(frame, "Power output",          args["power-output"])
  ret[#ret+1] = p._row_simple(frame, "Maximum temperature",  args["maximum-temperature"])
  ret[#ret+1] = p._row_simple(frame, "Fluid consumption",    args["fluid-consumption"])
  ret[#ret+1] = p._row_simple(frame, "Shield hitpoints",      args["shield"])
  ret[#ret+1] = p._row_type  (frame, "Energy per hitpoint",  args["energy-per-hitpoint"])
  ret[#ret+1] = p._row_simple(frame, "Maximum recharge speed",args["maximum-recharge-speed"])
  ret[#ret+1] = p._row_simple(frame, "Crafting speed",        args["crafting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Pumping speed",        args["pumping-speed"])
  ret[#ret+1] = p._row_simple(frame, "Mining time",          args["mining-time"])
  ret[#ret+1] = p._row_simple(frame, "Speed",                args["speed"])
  ret[#ret+1] = p._row_simple(frame, "Productivity",          args["productivity"])
  ret[#ret+1] = p._row_simple(frame, "Mining speed",          args["mining-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Mining area",          args["mining-area"],          "tiles")
  ret[#ret+1] = p._row_type  (frame, "Fuel value",            args["fuel-value"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle acceleration",  args["vehicle-acceleration"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle top speed",    args["vehicle-top-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Supply area",          args["supply-area"],          "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Wire reach",            args["wire-reach"],            "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Construction area",    args["construction-area"],    "tiles")
  ret[#ret+1] = p._row_simple(frame, "Pollution",            args["pollution"])
  ret[#ret+1] = p._row_unit  (frame, sa .. "Spoil time",      args["spoil-time"],            "minutes")
  ret[#ret+1] = p._row_simple(frame, "Weight",                args["weight"])
  ret[#ret+1] = p._row_unit  (frame, "Module slots",          args["modules"],              "slots")
  if not p._empty_arg(args["prototype-type"]) then
    local prototype_type = frame:expandTemplate{title = 'Prototype page', args = {args["prototype-type"]}}
    ret[#ret+1] = p._row(frame, {"Prototype type", args["prototype-type"], prototype_type})
  end
  ret[#ret+1] = p._row_simple(frame, "Internal name",        args["internal-name"])
  ret[#ret+1] = p._vrow_item (frame, "Accepted equipment",    args["equipment"])
  ret[#ret+1] = p._vrow_tech (frame, "Required technologies", p._arg_or(args["space-age-required-technologies"], args["required-technologies"]))
  ret[#ret+1] = p._vrow_tech (frame, "Allows",                p._arg_or(args["space-age-allows"], args["allows"]))
  ret[#ret+1] = p._vrow_item (frame, "Effects",              p._arg_or(args["space-age-effects"], args["effects"]))
  ret[#ret+1] = p._vrow_tech (frame, "Boosting technologies", args["boosting-technologies"])
  ret[#ret+1] = p._vrow_item (frame, "Produced by",          args["producers"])
  ret[#ret+1] = p._vrow_item (frame, "Consumed by",          p._arg_or(args["space-age-consumers"], args["consumers"]))
  ret[#ret+1] = p._vrow_item (frame, "Valid fuel",            args["valid-fuel"])
  ret[#ret+1] = p._vrow_item (frame, "Used as fuel by",      args["used-as-fuel-by"])
  ret[#ret+1] = p._vrow_item (frame, sa .. "Recyling results",args["recycling-results"])
  ret[#ret+1] = p._extra    (frame,                          args["extra2"])
  return table.concat(ret)
end
function p._translate(frame, str)
  return frame:expandTemplate{title = "Translation", args = {str}}
end
function p._row_simple(frame, label, arg)
  if p._empty_arg(arg) then return end
 
  return p._row(frame, {label, arg})
end
function p._row_unit(frame, label, arg, unit)
  if p._empty_arg(arg) then return end
 
  return p._row(frame, {label, arg, arg .. " " .. p._translate(frame, unit)})
end
function p._row_type(frame, label, arg)
  if p._empty_arg(arg) then return end
 
  return p._row(frame, {label, arg, frame:expandTemplate{title = "Type", args = {arg}}})
end
function p._row_item(frame, label, arg)
  if p._empty_arg(arg) then return end
 
  return p._row(frame, {label, arg, p._item_parsing(frame, arg)})
end
function p._vrow_item(frame, label, arg)
  if p._empty_arg(arg) then return end
 
  return p._vrow(frame, {label, arg, p._item_parsing(frame, arg)})
end
function p._vrow_tech(frame, label, arg)
  if p._empty_arg(arg) then return end
 
  return p._vrow(frame, {label, arg, p._technology_parsing(frame, {arg, color = "228B22"})})
end
function p._row(frame, args)
  if p._empty_arg(args[2]) then
    return
  end
  local label = p._translate(frame, args[1])
  return string.format(
[[<tr class="border-top">
<td>
%s
</td>
<td>
%s
</td>
</tr>]],
    label,
    args[3] or args[2])
end
function p._vrow(frame, args)
  if p._empty_arg(args[2]) then
    return
  end
  local label = p._translate(frame, args[1])
  return string.format(
[[<tr class="border-top">
<td colspan=2>
%s
</td>
</tr>
<tr>
<td class="infobox-vrow-value" colspan=2>
%s
</td>
</tr>]],
    label,
    args[3] or args[2])
end
function p._extra(frame, arg)
  if p._empty_arg(arg) then
    return
  end
  local label = p._translate(frame, arg)
  return string.format(
[[<tr class="border-top">
<td colspan=2 class="infobox-extra">
%s
</td>
</tr>]],
    label)
end


function p.technology_parsing(frame)
function p.technology_parsing(frame)
   local args = frame.args
   return p._technology_parsing(frame, frame.args)
   if args[1] == "?" then
end
     return args[1] -- same as old template, likely not needed
 
function p._technology_parsing(frame, args)
   if args[1] == "?" or p._empty_arg(args[1]) then
     return args[1]
   end
   end


Line 29: Line 368:


function p.item_parsing(frame)
function p.item_parsing(frame)
   local args = frame.args
   return p._item_parsing(frame, frame.args[1])
   if args[1] == "?" then
end
     return args[1] -- same as old template, likely not needed
 
function p._item_parsing(frame, item_string)
   if item_string == "?" or p._empty_arg(item_string) then
     return item_string
   end
   end


   local ret = {}
   local ret = {}
   local items = p._split(args[1], "+")
   local items = p._split(item_string, "+")
   for _, item in ipairs(items) do
   for _, item in ipairs(items) do
     local icon_args = p._split(item, ",")
     local icon_args = p._split(item, ",")
Line 44: Line 386:
end
end


function p.crafting_parsing(frame)
  return p._crafting_parsing(frame, frame.args[1])
end


function p.crafting_parsing(frame)
function p._crafting_parsing(frame, recipe)
  local args = frame.args
   if recipe == "?" or p._empty_arg(recipe) then
   if args[1] == "?" then
     return recipe
     return args[1] -- same as old template, likely not needed
   end
   end
    
    
   local recipe_parts = p._split(args[1], "=")   
   local recipe_parts = p._split(recipe, "=")   
   local ingredients = p._crafting_parsing_split(frame, recipe_parts[1])
   local ingredients = p._crafting_parsing_split(frame, recipe_parts[1])
    
    
Line 66: Line 410:


function p.crafting_raw(frame)
function p.crafting_raw(frame)
   local args = frame.args
   return p._crafting_raw(frame, frame.args[1])
   if args[1] == "?" then
end
     return args[1] -- same as old template, likely not needed
 
function p._crafting_raw(frame, recipe)
   if recipe == "?" or p._empty_arg(recipe) then
     return recipe
   end
   end


   local recipe_parts = p._split(args[1], "=")
   local recipe_parts = p._split(recipe, "=")
   return p._crafting_parsing_split(frame, recipe_parts[1])
   return p._crafting_parsing_split(frame, recipe_parts[1])
end
end
Line 88: Line 435:
   end
   end
   return table.concat(ret, "+")
   return table.concat(ret, "+")
end
function p._arg_or(arg, default)
  if p._empty_arg(arg) then
    return default
  else
    return arg
  end
end
-- @param arg string The argument to check
-- @return boolean @Whether the argument is an whitespace string or empty string or nil
function p._empty_arg(arg)
  if type(arg) == "string" then
    return not string.find(arg, "%S")
  else
    return not arg
  end
end
end



Revision as of 12:25, 24 October 2024

Lua methods used by Template:Infobox. Uses Lua methods from Module:Infobox/parsing and Module:Util.

Edit this documentation on Module:Infobox/doc.

Methods

base_tab

space_age_tab


local p = {}

function p.base_tab(frame) -- no prefix for args. Used for vanilla tab or if no tabs
  local args = frame:getParent().args
  local ret = {}
  local sa = frame:expandTemplate{title = "Space age"} .. " "

  if not p._empty_arg(args["recipe"]) then
    ret[#ret+1] = p._vrow(frame, {"Recipe",    args["recipe"], p._crafting_parsing(frame, args["recipe"])})
    ret[#ret+1] = p._vrow(frame, {"Total raw", args["recipe"], p._crafting_raw(frame, p._arg_or(args["total-raw"], args["recipe"]))})
  end

  if not p._empty_arg(args["cost"]) then
    local cost = p._item_parsing(frame, args["cost"]) .. ((not p._empty_arg(args["cost-multiplier"])) and "✖ <big>" .. args["cost-multiplier"] .. "</big>" or "")
    ret[#ret+1] = p._vrow(frame, {"Cost", args["cost"], cost})
  end

  ret[#ret+1] = p._extra     (frame,                          args["extra1"])
  
  if not p._empty_arg(args["map-color"]) then
    local color = frame:expandTemplate{title = 'Color', args = {args["map-color"]}}
    ret[#ret+1] = p._row(frame, {"Map color", args["map-color"], color})
  end

  ret[#ret+1] = p._row_simple(frame, "Map icon",              args["map-icon"])
  ret[#ret+1] = p._row_simple(frame, "Added in",              args["added-in"])
  ret[#ret+1] = p._row_simple(frame, "Walking speed",         args["walking-speed"])
  ret[#ret+1] = p._row_simple(frame, "Storage size",          args["storage-size"])
  ret[#ret+1] = p._row_simple(frame, "Fluid storage volume",  args["fluid-storage-volume"])

  if not p._empty_arg(args["expected-resources"]) then
    local label = p._translate(frame, "Expected resources")
    ret[#ret+1] = string.format(
[[<tr class="border-top">
<td>
%s
</td>
<td style="width: 70%%;">
%s
</td>
</tr>]], label, args["expected-resources"])
  end

  ret[#ret+1] = p._row_simple(frame, "Health",                args["health"])
  ret[#ret+1] = p._row_unit  (frame, "Restores",              args["restores"],             "health")
  ret[#ret+1] = p._row_simple(frame, "Resistances",           args["resistance"])
  ret[#ret+1] = p._row_unit  (frame, "Lifespan",              args["lifespan"],             "seconds")
  ret[#ret+1] = p._row_simple(frame, "Inventory size bonus",  args["inventory-size-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Equipment grid size",   args["grid-size"])
  ret[#ret+1] = p._row_simple(frame, "Stack size",            args["stack-size"])
  ret[#ret+1] = p._row_simple(frame, "Range",                 args["range"])
  ret[#ret+1] = p._row_simple(frame, "Shooting speed",        args["shooting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Damage",                args["damage"])
  ret[#ret+1] = p._row_simple(frame, "Damage bonus",          args["damage-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Cluster size",          args["cluster-size"])
  ret[#ret+1] = p._row_simple(frame, "Area of effect size",   args["area-of-effect-size"])
  ret[#ret+1] = p._row_simple(frame, "Durability",            args["durability"])
  ret[#ret+1] = p._row_simple(frame, "Magazine size",         args["magazine-size"])
  ret[#ret+1] = p._row_item  (frame, "Ammunition",            args["ammunition"])
  ret[#ret+1] = p._vrow_item (frame, "Used as ammunition by", args["used-as-ammo-by"])
  ret[#ret+1] = p._row_simple(frame, "Efficiency",            args["efficiency"])
  ret[#ret+1] = p._row_simple(frame, "Dimensions",            args["dimensions"])
  ret[#ret+1] = p._row_type  (frame, "Energy consumption",    args["energy"])
  ret[#ret+1] = p._row_type  (frame, "Drain",                 args["drain"])
  ret[#ret+1] = p._row_type  (frame, "Robot recharge rate",   args["robot-recharge-rate"])
  ret[#ret+1] = p._row_type  (frame, "Internal buffer recharge rate", args["internal-buffer-recharge-rate"])
  ret[#ret+1] = p._vrow_item (frame, "Placed in",             args["equipped-in"])
  ret[#ret+1] = p._row_simple(frame, "Robot limit",           args["robot-limit"])
  ret[#ret+1] = p._row_simple(frame, "Repair speed",          args["repair-speed"])
  ret[#ret+1] = p._row_simple(frame, "Charging stations",     args["charging-stations"])
  ret[#ret+1] = p._row_simple(frame, "Belt speed",            args["belt-speed"])
  ret[#ret+1] = p._row_simple(frame, "Movement bonus",        args["movement-bonus"])
  ret[#ret+1] = p._row_type  (frame, "Energy capacity",       args["energy-capacity"])
  ret[#ret+1] = p._row_simple(frame, "Power input",           args["power-input"])
  ret[#ret+1] = p._row_simple(frame, "Power output",          args["power-output"])
  ret[#ret+1] = p._row_simple(frame, "Maximum temperature",   args["maximum-temperature"])
  ret[#ret+1] = p._row_simple(frame, "Fluid consumption",     args["fluid-consumption"])
  ret[#ret+1] = p._row_simple(frame, "Shield hitpoints",      args["shield"])
  ret[#ret+1] = p._row_type  (frame, "Energy per hitpoint",   args["energy-per-hitpoint"])
  ret[#ret+1] = p._row_simple(frame, "Maximum recharge speed",args["maximum-recharge-speed"])
  ret[#ret+1] = p._row_simple(frame, "Crafting speed",        args["crafting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Pumping speed",         args["pumping-speed"])
  ret[#ret+1] = p._row_simple(frame, "Mining time",           args["mining-time"])
  ret[#ret+1] = p._row_simple(frame, "Speed",                 args["speed"])
  ret[#ret+1] = p._row_simple(frame, "Productivity",          args["productivity"])
  ret[#ret+1] = p._row_simple(frame, "Mining speed",          args["mining-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Mining area",           args["mining-area"],           "tiles")
  ret[#ret+1] = p._row_type  (frame, "Fuel value",            args["fuel-value"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle acceleration",  args["vehicle-acceleration"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle top speed",     args["vehicle-top-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Supply area",           args["supply-area"],           "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Wire reach",            args["wire-reach"],            "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Construction area",     args["construction-area"],     "tiles")
  ret[#ret+1] = p._row_simple(frame, "Pollution",             args["pollution"])
  ret[#ret+1] = p._row_unit  (frame, sa .. "Spoil time",      args["spoil-time"],            "minutes")
  ret[#ret+1] = p._row_simple(frame, "Weight",                args["weight"])
  ret[#ret+1] = p._row_unit  (frame, "Module slots",          args["modules"],               "slots")

  if not p._empty_arg(args["prototype-type"]) then
    local prototype_type = frame:expandTemplate{title = 'Prototype page', args = {args["prototype-type"]}}
    ret[#ret+1] = p._row(frame, {"Prototype type", args["prototype-type"], prototype_type})
  end
  ret[#ret+1] = p._row_simple(frame, "Internal name",         args["internal-name"])

  ret[#ret+1] = p._vrow_item (frame, "Accepted equipment",    args["equipment"])
  ret[#ret+1] = p._vrow_tech (frame, "Required technologies", args["required-technologies"])
  ret[#ret+1] = p._vrow_tech (frame, "Allows",                args["allows"])
  ret[#ret+1] = p._vrow_item (frame, "Effects",               args["effects"])
  ret[#ret+1] = p._vrow_tech (frame, "Boosting technologies", args["boosting-technologies"])
  ret[#ret+1] = p._vrow_item (frame, "Produced by",           args["producers"])
  ret[#ret+1] = p._vrow_item (frame, "Consumed by",           args["consumers"])
  ret[#ret+1] = p._vrow_item (frame, "Valid fuel",            args["valid-fuel"])
  ret[#ret+1] = p._vrow_item (frame, "Used as fuel by",       args["used-as-fuel-by"])
  ret[#ret+1] = p._vrow_item (frame, sa .. "Recyling results",args["recycling-results"])
  ret[#ret+1] = p._extra     (frame,                          args["extra2"])

  return table.concat(ret)
end

function p.space_age_tab(frame) -- space-age prefix for some args. Used for space age mod tab
  local args = frame:getParent().args
  local ret = {}
  local sa = frame:expandTemplate{title = "Space age"} .. " "
  
  local recipe = p._arg_or(args["space-age-recipe"], args["recipe"])
  if not p._empty_arg(recipe) then
    ret[#ret+1] = p._vrow(frame, {"Recipe",    recipe, p._crafting_parsing(frame, recipe)})
    local vanilla_total_raw = p._arg_or(args["total-raw"], args["recipe"])
    ret[#ret+1] = p._vrow(frame, {"Total raw", recipe, p._crafting_raw(frame, p._arg_or(args["space-age-total-raw"],  p._arg_or(args["space-age-recipe"], vanilla_total_raw)))})
  end

  local cost_arg = p._arg_or(args["space-age-cost"], args["cost"])
  if not p._empty_arg(cost_arg) then
    local cost_multiplier_arg = p._arg_or(args["space-age-cost-multiplier"], args["cost-multiplier"])
    local cost = p._item_parsing(frame, cost_arg) .. ((not p._empty_arg(cost_multiplier_arg)) and "✖ <big>" .. cost_multiplier_arg .. "</big>" or "")
    ret[#ret+1] = p._vrow(frame, {"Cost", cost_arg, cost})
  end

  ret[#ret+1] = p._extra     (frame,                          args["extra1"])
  
  if not p._empty_arg(args["map-color"]) then
    local color = frame:expandTemplate{title = 'Color', args = {args["map-color"]}}
    ret[#ret+1] = p._row(frame, {"Map color", args["map-color"], color})
  end

  ret[#ret+1] = p._row_simple(frame, "Map icon",              args["map-icon"])
  ret[#ret+1] = p._row_simple(frame, "Added in",              args["added-in"])
  ret[#ret+1] = p._row_simple(frame, "Walking speed",         args["walking-speed"])
  ret[#ret+1] = p._row_simple(frame, "Storage size",          args["storage-size"])
  ret[#ret+1] = p._row_simple(frame, "Fluid storage volume",  args["fluid-storage-volume"])

  if not p._empty_arg(args["expected-resources"]) then
    local label = p._translate(frame, "Expected resources")
    ret[#ret+1] = string.format(
[[<tr class="border-top">
<td>
%s
</td>
<td style="width: 70%%;">
%s
</td>
</tr>]], label, args["expected-resources"])
  end

  ret[#ret+1] = p._row_simple(frame, "Health",                args["health"])
  ret[#ret+1] = p._row_unit  (frame, "Restores",              args["restores"],             "health")
  ret[#ret+1] = p._row_simple(frame, "Resistances",           args["resistance"])
  ret[#ret+1] = p._row_unit  (frame, "Lifespan",              args["lifespan"],             "seconds")
  ret[#ret+1] = p._row_simple(frame, "Inventory size bonus",  args["inventory-size-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Equipment grid size",   args["grid-size"])
  ret[#ret+1] = p._row_simple(frame, "Stack size",            args["stack-size"])
  ret[#ret+1] = p._row_simple(frame, "Range",                 args["range"])
  ret[#ret+1] = p._row_simple(frame, "Shooting speed",        args["shooting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Damage",                args["damage"])
  ret[#ret+1] = p._row_simple(frame, "Damage bonus",          args["damage-bonus"])
  ret[#ret+1] = p._row_simple(frame, "Cluster size",          args["cluster-size"])
  ret[#ret+1] = p._row_simple(frame, "Area of effect size",   args["area-of-effect-size"])
  ret[#ret+1] = p._row_simple(frame, "Durability",            args["durability"])
  ret[#ret+1] = p._row_simple(frame, "Magazine size",         args["magazine-size"])
  ret[#ret+1] = p._row_item  (frame, "Ammunition",            args["ammunition"])
  ret[#ret+1] = p._vrow_item (frame, "Used as ammunition by", args["used-as-ammo-by"])
  ret[#ret+1] = p._row_simple(frame, "Efficiency",            args["efficiency"])
  ret[#ret+1] = p._row_simple(frame, "Dimensions",            args["dimensions"])
  ret[#ret+1] = p._row_type  (frame, "Energy consumption",    args["energy"])
  ret[#ret+1] = p._row_type  (frame, "Drain",                 args["drain"])
  ret[#ret+1] = p._row_type  (frame, "Robot recharge rate",   args["robot-recharge-rate"])
  ret[#ret+1] = p._row_type  (frame, "Internal buffer recharge rate", args["internal-buffer-recharge-rate"])
  ret[#ret+1] = p._vrow_item (frame, "Placed in",             args["equipped-in"])
  ret[#ret+1] = p._row_simple(frame, "Robot limit",           args["robot-limit"])
  ret[#ret+1] = p._row_simple(frame, "Repair speed",          args["repair-speed"])
  ret[#ret+1] = p._row_simple(frame, "Charging stations",     args["charging-stations"])
  ret[#ret+1] = p._row_simple(frame, "Belt speed",            args["belt-speed"])
  ret[#ret+1] = p._row_simple(frame, "Movement bonus",        args["movement-bonus"])
  ret[#ret+1] = p._row_type  (frame, "Energy capacity",       args["energy-capacity"])
  ret[#ret+1] = p._row_simple(frame, "Power input",           args["power-input"])
  ret[#ret+1] = p._row_simple(frame, "Power output",          args["power-output"])
  ret[#ret+1] = p._row_simple(frame, "Maximum temperature",   args["maximum-temperature"])
  ret[#ret+1] = p._row_simple(frame, "Fluid consumption",     args["fluid-consumption"])
  ret[#ret+1] = p._row_simple(frame, "Shield hitpoints",      args["shield"])
  ret[#ret+1] = p._row_type  (frame, "Energy per hitpoint",   args["energy-per-hitpoint"])
  ret[#ret+1] = p._row_simple(frame, "Maximum recharge speed",args["maximum-recharge-speed"])
  ret[#ret+1] = p._row_simple(frame, "Crafting speed",        args["crafting-speed"])
  ret[#ret+1] = p._row_simple(frame, "Pumping speed",         args["pumping-speed"])
  ret[#ret+1] = p._row_simple(frame, "Mining time",           args["mining-time"])
  ret[#ret+1] = p._row_simple(frame, "Speed",                 args["speed"])
  ret[#ret+1] = p._row_simple(frame, "Productivity",          args["productivity"])
  ret[#ret+1] = p._row_simple(frame, "Mining speed",          args["mining-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Mining area",           args["mining-area"],           "tiles")
  ret[#ret+1] = p._row_type  (frame, "Fuel value",            args["fuel-value"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle acceleration",  args["vehicle-acceleration"])
  ret[#ret+1] = p._row_simple(frame, "Vehicle top speed",     args["vehicle-top-speed"])
  ret[#ret+1] = p._row_unit  (frame, "Supply area",           args["supply-area"],           "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Wire reach",            args["wire-reach"],            "tiles")
  ret[#ret+1] = p._row_unit  (frame, "Construction area",     args["construction-area"],     "tiles")
  ret[#ret+1] = p._row_simple(frame, "Pollution",             args["pollution"])
  ret[#ret+1] = p._row_unit  (frame, sa .. "Spoil time",      args["spoil-time"],            "minutes")
  ret[#ret+1] = p._row_simple(frame, "Weight",                args["weight"])
  ret[#ret+1] = p._row_unit  (frame, "Module slots",          args["modules"],               "slots")

  if not p._empty_arg(args["prototype-type"]) then
    local prototype_type = frame:expandTemplate{title = 'Prototype page', args = {args["prototype-type"]}}
    ret[#ret+1] = p._row(frame, {"Prototype type", args["prototype-type"], prototype_type})
  end
  ret[#ret+1] = p._row_simple(frame, "Internal name",         args["internal-name"])

  ret[#ret+1] = p._vrow_item (frame, "Accepted equipment",    args["equipment"])
  ret[#ret+1] = p._vrow_tech (frame, "Required technologies", p._arg_or(args["space-age-required-technologies"], args["required-technologies"]))
  ret[#ret+1] = p._vrow_tech (frame, "Allows",                p._arg_or(args["space-age-allows"], args["allows"]))
  ret[#ret+1] = p._vrow_item (frame, "Effects",               p._arg_or(args["space-age-effects"], args["effects"]))
  ret[#ret+1] = p._vrow_tech (frame, "Boosting technologies", args["boosting-technologies"])
  ret[#ret+1] = p._vrow_item (frame, "Produced by",           args["producers"])
  ret[#ret+1] = p._vrow_item (frame, "Consumed by",           p._arg_or(args["space-age-consumers"], args["consumers"]))
  ret[#ret+1] = p._vrow_item (frame, "Valid fuel",            args["valid-fuel"])
  ret[#ret+1] = p._vrow_item (frame, "Used as fuel by",       args["used-as-fuel-by"])
  ret[#ret+1] = p._vrow_item (frame, sa .. "Recyling results",args["recycling-results"])
  ret[#ret+1] = p._extra     (frame,                          args["extra2"])

  return table.concat(ret)
end

function p._translate(frame, str)
  return frame:expandTemplate{title = "Translation", args = {str}}
end

function p._row_simple(frame, label, arg)
  if p._empty_arg(arg) then return end
  
  return p._row(frame, {label, arg})
end

function p._row_unit(frame, label, arg, unit)
  if p._empty_arg(arg) then return end
  
  return p._row(frame, {label, arg, arg .. " " .. p._translate(frame, unit)})
end

function p._row_type(frame, label, arg)
  if p._empty_arg(arg) then return end
  
  return p._row(frame, {label, arg, frame:expandTemplate{title = "Type", args = {arg}}})
end

function p._row_item(frame, label, arg)
  if p._empty_arg(arg) then return end
  
  return p._row(frame, {label, arg, p._item_parsing(frame, arg)})
end

function p._vrow_item(frame, label, arg)
  if p._empty_arg(arg) then return end
  
  return p._vrow(frame, {label, arg, p._item_parsing(frame, arg)})
end

function p._vrow_tech(frame, label, arg)
  if p._empty_arg(arg) then return end
  
  return p._vrow(frame, {label, arg, p._technology_parsing(frame, {arg, color = "228B22"})})
end

function p._row(frame, args)
  if p._empty_arg(args[2]) then
    return
  end

  local label = p._translate(frame, args[1])

  return string.format(
[[<tr class="border-top">
<td>
%s
</td>
<td>
%s
</td>
</tr>]],
    label,
    args[3] or args[2])
end

function p._vrow(frame, args)
  if p._empty_arg(args[2]) then
    return
  end

  local label = p._translate(frame, args[1])

  return string.format(
[[<tr class="border-top">
<td colspan=2>
%s
</td>
</tr>
<tr>
<td class="infobox-vrow-value" colspan=2>
%s
</td>
</tr>]],
    label,
    args[3] or args[2])
end

function p._extra(frame, arg)
  if p._empty_arg(arg) then
    return
  end

  local label = p._translate(frame, arg)

  return string.format(
[[<tr class="border-top">
<td colspan=2 class="infobox-extra">
%s
</td>
</tr>]],
    label)
end

function p.technology_parsing(frame)
  return p._technology_parsing(frame, frame.args)
end

function p._technology_parsing(frame, args)
  if args[1] == "?" or p._empty_arg(args[1]) then
    return args[1]
  end

  local ret = {}
  local techs = p._split(args[1], "+")
  for _, tech in ipairs(techs) do
    local tech_parts = p._split(tech, ",")
    local tech_name = tech_parts[1] .. " (research)"
    if not p._page_exists(tech_name) then -- fall back to plain name if page with " (research)" doesn't exist
      tech_name = tech_parts[1]
    end

    local tech_level = tech_parts[2] or ""
    if tech_level == "" then -- fall back to level from tech name if none given manually
      local second_to_last_char = string.sub(tech_parts[1], -2, -2)
      if second_to_last_char == " " then
        tech_level = string.sub(tech_parts[1], -1) -- last character should be the level of the tech
      end
    end
    ret[#ret+1] = frame:expandTemplate{title = 'icon/special', args = {tech_name, tech_level, color=args.color or "999"}}
  end
  return table.concat(ret)
end

function p.item_parsing(frame)
  return p._item_parsing(frame, frame.args[1])
end

function p._item_parsing(frame, item_string)
  if item_string == "?" or p._empty_arg(item_string) then
    return item_string
  end

  local ret = {}
  local items = p._split(item_string, "+")
  for _, item in ipairs(items) do
    local icon_args = p._split(item, ",")
    icon_args[2] = icon_args[2] or ""
    ret[#ret+1] = frame:expandTemplate{title = 'icon/special', args = icon_args}
  end
  return table.concat(ret)
end

function p.crafting_parsing(frame)
  return p._crafting_parsing(frame, frame.args[1])
end

function p._crafting_parsing(frame, recipe)
  if recipe == "?" or p._empty_arg(recipe) then
    return recipe
  end
  
  local recipe_parts = p._split(recipe, "=")  
  local ingredients = p._crafting_parsing_split(frame, recipe_parts[1])
  
  local products = recipe_parts[2]
  if products then
    products = "&rarr; " .. p._crafting_parsing_split(frame, products)
  else
    local item_name = frame:expandTemplate{title = 'No language suffix/No namespace'}
    products = "&rarr; " .. frame:expandTemplate{title = 'icon/special', args = {item_name, "1"}}
  end
  
  return ingredients .. products
end

function p.crafting_raw(frame)
  return p._crafting_raw(frame, frame.args[1])
end

function p._crafting_raw(frame, recipe)
  if recipe == "?" or p._empty_arg(recipe) then
    return recipe
  end

  local recipe_parts = p._split(recipe, "=")
  return p._crafting_parsing_split(frame, recipe_parts[1])
end

-- @param frame
-- @param str string A list of "item, count" separated by "+"
-- @return string @The resulting icons with a "+" between each icon
function p._crafting_parsing_split(frame, str)
  local ret = {}
  local items = p._split(str, "+")
  for _, item in ipairs(items) do
    local item_parts = p._split(item, ",")
    local item_count = item_parts[2] or "1" -- fall back to 1, not empty!
    item_count = frame:expandTemplate{title = 'Crop', args = {item_count}}
    ret[#ret+1] = frame:expandTemplate{title = 'icon/special', args = {item_parts[1], item_count}}
  end
  return table.concat(ret, "+")
end

function p._arg_or(arg, default)
  if p._empty_arg(arg) then
    return default
  else
    return arg
  end
end

-- @param arg string The argument to check
-- @return boolean @Whether the argument is an whitespace string or empty string or nil
function p._empty_arg(arg)
  if type(arg) == "string" then
    return not string.find(arg, "%S")
  else
    return not arg
  end
end

-- @param inputstr string A string to be split
-- @param separator string The separator
-- @return string[] @The separated strings, without the separator
function p._split(inputstr, separator)
  local result = {}

  for str in string.gmatch(inputstr, "([^"..separator.."]+)") do
    table.insert(result, mw.text.trim(str))
  end

  return result
end

-- @param page_title string The title of the page
-- @return boolean
function p._page_exists(page_title)
  if page_title and page_title ~= "" then
    return mw.title.new(page_title).exists
  else
    return false
  end
end

return p