Module:Chem box: Difference between revisions
From Space Station 14 Wiki
Created page with "local p = {} --p stands for package local getArgs = require('Module:Arguments').getArgs local yesNo = require('Module:Yesno') local chem_data = mw.loadJsonData("User:UpAndLeaves/chemData.json") local current_frame = mw.getCurrentFrame() local beaker_el = current_frame.expandTemplate { title = 'Beaker' } -- ==================== local function numeric_table_length(t) local count = 0 for _ in ipairs(t) do count = count + 1 end return count end local f..." |
m uppercased [K]elvin |
||
(27 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} --p stands for package | local p = {} --p stands for package | ||
local getArgs = require( | local getArgs = require("Module:Arguments").getArgs | ||
local yesNo = require('Module: | local yesNo = require("Module:Yesno") | ||
local reagent_card = require('Module:Chemistry').reagent_card | |||
local chem_data = mw.loadJsonData(" | local chem_data = mw.loadJsonData("Module:Chem box/chem data.json") | ||
local current_frame = mw.getCurrentFrame() | local current_frame = mw.getCurrentFrame() | ||
-- ==================== | -- ==================== | ||
local function numeric_table_length(t) | local function numeric_table_length(t) | ||
local count = 0 | |||
for _ in ipairs(t) do | |||
count = count + 1 | |||
end | |||
return count | |||
end | end | ||
local function table_length(t) | local function table_length(t) | ||
local count = 0 | |||
for _ in pairs(t) do | |||
count = count + 1 | |||
end | |||
return count | |||
end | end | ||
local function table_has_value(tab, val) | local function table_has_value(tab, val) | ||
for _, value in ipairs(tab) do | |||
if value == val then | |||
return true | |||
end | |||
end | |||
return false | |||
end | end | ||
local function assert_value_not_nil(value, error_message) | local function assert_value_not_nil(value, error_message) | ||
if value == nil then | |||
if error_message == nil then | |||
error("value is nil") | |||
else | |||
error(error_message) | |||
end | |||
end | |||
end | end | ||
Line 49: | Line 49: | ||
-- Source: https://stackoverflow.com/a/2421746 | -- Source: https://stackoverflow.com/a/2421746 | ||
local function capitalize(str) | local function capitalize(str) | ||
return (str:gsub("^%l", string.upper)) | |||
end | end | ||
local function passthrough_assert_true(value, valueToReturnIfTrue, errorMessageOnFalse) | local function passthrough_assert_true(value, valueToReturnIfTrue, errorMessageOnFalse) | ||
if value then | |||
return valueToReturnIfTrue | |||
else | |||
error(errorMessageOnFalse) | |||
end | |||
end | |||
local function find_first_numeric_table_item_matching_condition(table, condition) | |||
for i, item in ipairs(table) do | |||
if condition(item, i, table) then | |||
return item | |||
end | |||
end | |||
end | |||
-- table concat function because ofcourse fucking table.concat doesn't work | |||
local function concat_numberic_table(tbl, sep) | |||
local temp_table = {} | |||
for i = 1, numeric_table_length(tbl) do | |||
table.insert(temp_table, tbl[i]) | |||
end | |||
return table.concat(temp_table, sep) | |||
end | |||
local function ternary_strict(valueToCheck, valueIfTrue, valueIfFalse) | |||
if valueToCheck == true then | |||
return valueIfTrue | |||
else | |||
return valueIfFalse | |||
end | |||
end | |||
local function table_map(tbl, mapFn) | |||
local res = {} | |||
for k, v in pairs(tbl) do | |||
table.insert(res, mapFn(v, k, tbl)) | |||
end | |||
return res | |||
end | |||
local function num_table_reduce(tbl, reduceFn, initial_value) | |||
local out = initial_value | |||
for i, v in ipairs(tbl) do | |||
out = reduceFn(out, v, i, tbl) | |||
end | end | ||
return out | |||
end | end | ||
local function | local function table_filter(tbl, filterFn) | ||
for | local out = {} | ||
if | |||
for k, v in pairs(tbl) do | |||
if filterFn(v, k, tbl) then out[k] = v end | |||
end | end | ||
return out | |||
end | end | ||
-- Sorts a table into a new table. | |||
-- An alternative to cases such as when table.sort is not working, | |||
-- like when trying to sort JSON tables (thanks fuckass lua). | |||
local function table_to_sorted(tbl, sortfn) | |||
local keys = {} | |||
for key, _ in pairs(tbl) do | |||
table.insert(keys, key) | |||
end | |||
table.sort(keys, function(keyA, keyB) return sortfn(tbl[keyA], tbl[keyB]) end) | |||
local t2 = {} | |||
for _, key in ipairs(keys) do | |||
table.insert(t2, tbl[key]) | |||
end | |||
return t2 | |||
end | |||
-- ==================== | -- ==================== | ||
Line 76: | Line 143: | ||
-- Set `no_error` to `true` to return `nil` instead. | -- Set `no_error` to `true` to return `nil` instead. | ||
function p.lookup_reagent(query, no_error) | function p.lookup_reagent(query, no_error) | ||
local query_lower = string.lower(query) | |||
for _, reagent in ipairs(chem_data) do | |||
assert_value_not_nil(reagent.id) | |||
assert_value_not_nil(reagent.name) | |||
if reagent.id == query then | |||
return reagent | |||
end | |||
if string.lower(reagent.name) == query_lower then | |||
return reagent | |||
end | |||
end | |||
return passthrough_assert_true(no_error, nil, "failed to lookup reagent: no match was found") | |||
end | |||
local function get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order, a, b) | |||
return function (a, b) | |||
return map_of_reagent_name_or_id_to_order[string.lower(a.name or a.id)] | |||
< map_of_reagent_name_or_id_to_order[string.lower(b.name or b.id)] | |||
end | |||
end | |||
-- Sorts an array of reagents in place by name (or ID if no name), a to z. | |||
-- @param reagents Reagents to sort. | |||
-- @param to_sorted Whether to create a new sorted table instead of sorting in place. | |||
local function sort_reagents_az(reagents, to_sorted) | |||
local reagents_names_or_ids = table_map(reagents, function (reagent) | |||
return string.lower(reagent.name or reagent.id) | |||
end) | |||
table.sort(reagents_names_or_ids) | |||
local map_of_reagent_name_or_id_to_order = num_table_reduce(reagents_names_or_ids, function (accum, value, i) | |||
accum[value] = i | |||
return accum | |||
end, {}) | |||
if to_sorted then | |||
return table_to_sorted(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order)) | |||
else | |||
table.sort(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order)) | |||
end | |||
end | end | ||
Line 94: | Line 192: | ||
function p.generate_chem_box(frame) | function p.generate_chem_box(frame) | ||
local args = getArgs(frame) | |||
local query = args[1] | |||
assert_value_not_nil(query, "failed to generate chem box: query was not provided") | |||
-- =================== | |||
local reagent = p.lookup_reagent(query) | |||
assert_value_not_nil(reagent.color) | |||
assert_value_not_nil(reagent.name) | |||
assert_value_not_nil(reagent.recipes) | |||
assert_value_not_nil(reagent.effectStrings) | |||
assert_value_not_nil(reagent.description) | |||
assert_value_not_nil(reagent.physicalDescription) | |||
assert_value_not_nil(reagent.textColorTheme) | |||
local recipes_container_el = mw.html.create("div") | |||
if numeric_table_length(reagent.recipes) == 0 then | |||
local no_recipes_text_el = mw.html.create("span"):css("color", "gray"):node("No recipes") | |||
recipes_container_el:node(no_recipes_text_el) | |||
else | |||
for _, recipe in ipairs(reagent.recipes) do | |||
assert_value_not_nil(recipe.id) | |||
assert_value_not_nil(recipe.reactants) | |||
assert_value_not_nil(recipe.products) | |||
local recipe_box_template_args = {} | |||
recipe_box_template_args.name = recipe.id | |||
-- a list of catalist reactants. | |||
-- contains "id" and "amount" | |||
local catalyst_reactants = {} | |||
for reactant_i, reactant in ipairs(recipe.reactants) do | |||
local reactant_name = capitalize(reactant.name) | |||
local reactant_amount = reactant.count | |||
local reactant_is_catalyst = reactant.isCatalyst or false | |||
local label = reactant_name | |||
local link = reactant.id | |||
if reactant_is_catalyst then | |||
table.insert(catalyst_reactants, { | |||
name = reactant_name, | |||
id = reactant_id, | |||
amount = reactant_amount, | |||
}) | |||
end | |||
local component_el = current_frame:expandTemplate({ | |||
title = "Recipe Component", | |||
args = { | |||
item = label, | |||
amount = reactant_amount, | |||
itemlink = link | |||
-- TODO | |||
-- image = "" | |||
}, | |||
}) | |||
recipe_box_template_args["component-" .. reactant_i] = component_el | |||
end | |||
local temperature_args = {} | |||
if recipe.minTemp ~= nil or recipe.maxTemp ~= nil then | |||
local temperature_string = "" | |||
if recipe.minTemp ~= nil then | |||
temperature_string = tostring(recipe.minTemp) .. "K " | |||
end | |||
temperature_string = temperature_string .. "<" | |||
if recipe.maxTemp ~= nil then | |||
temperature_string = temperature_string .. " " .. tostring(recipe.maxTemp) .. "K" | |||
end | |||
temperature_args = {temperature = temperature_string} | |||
end | |||
local required_mixer = "Beaker" | |||
if recipe.requiredMixerCategories ~= nil then | |||
required_mixer = recipe.requiredMixerCategories[1] | |||
end | |||
recipe_box_template_args.transformer = current_frame:expandTemplate({ | |||
title = required_mixer, | |||
args = temperature_args | |||
}) | |||
for product_i, product in ipairs(recipe.products) do | |||
local component_el = current_frame:expandTemplate({ | |||
title = "Recipe Component", | |||
args = { | |||
item = capitalize(product.name), | |||
amount = product.count, | |||
-- TODO | |||
-- image = "" | |||
}, | |||
}) | |||
recipe_box_template_args["result-" .. product_i] = component_el | |||
end | |||
-- add catalysts as products, if any | |||
for catalyst_product_i, catalyst_product_entry in ipairs(catalyst_reactants) do | |||
local product_name = catalyst_product_entry.name | |||
local product_amount = catalyst_product_entry.amount | |||
local label = current_frame:expandTemplate({ | |||
title = "Tooltip", | |||
args = { | |||
'<sup class="quickbox-catalyst-note">+</sup>', | |||
'<span class="quickbox-catalyst-note-tooltip">This product is a catalyst reactant, remaining after the reaction.</span>', | |||
}, | |||
}) .. product_name | |||
local component_el = current_frame:expandTemplate({ | |||
title = "Recipe Component", | |||
args = { | |||
item = label, | |||
amount = product_amount, | |||
-- TODO | |||
-- image = "" | |||
}, | |||
}) | |||
local index = numeric_table_length(recipe.products) + catalyst_product_i | |||
recipe_box_template_args["result-" .. index] = component_el | |||
end | |||
local recipes_el = current_frame:expandTemplate({ | |||
title = "Recipe Box", | |||
args = recipe_box_template_args, | |||
}) | |||
recipes_container_el:node(recipes_el) | |||
end | |||
end | |||
local effects = current_frame:preprocess(concat_numberic_table(reagent.effectStrings, "")) | |||
return reagent_card { | |||
id = reagent.id, | |||
color = reagent.color, | |||
name = capitalize(reagent.name), | |||
recipes = tostring(recipes_container_el:allDone()), | |||
metabolisms = effects, | |||
desc = reagent.description, | |||
physicalDesc = reagent.physicalDescription, | |||
} | |||
end | |||
function p.generate_chem_boxes_for_all_reagents() | |||
local container_el = mw.html.create("div"):addClass("reagents-list") | |||
-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort | |||
local reagents_sorted = sort_reagents_az(chem_data, true) | |||
for _, reagent in ipairs(reagents_sorted) do | |||
assert_value_not_nil(reagent.id) | |||
container_el:node(p.generate_chem_box({ | |||
reagent.id, | |||
})) | |||
end | |||
return container_el:allDone() | |||
end | |||
function p.generate_chem_boxes_for_group(frame) | |||
local args = getArgs(frame) | |||
local query = args[1] | |||
assert_value_not_nil(query) | |||
-- ======== | |||
local query_lc = string.lower(query) | |||
local container_el = mw.html.create("div"):addClass("reagents-list") | |||
local reagents = table_filter(chem_data, function (reagent) | |||
return reagent.group | |||
and string.lower(reagent.group) == query_lc | |||
end) | |||
-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort, | |||
-- even when filtered smh | |||
reagents = sort_reagents_az(reagents, true) | |||
for _, reagent in ipairs(reagents) do | |||
container_el:node(p.generate_chem_box({ | |||
reagent.id, | |||
})) | |||
end | |||
return container_el:allDone() | |||
end | end | ||
return p | return p |
Latest revision as of 22:14, 10 June 2025
Module documentation
|
---|
View or edit this documentation • (about module documentation) |
Uses JSON data
This module uses JSON data pages:
Implements {{chem box}}.
local p = {} --p stands for package
local getArgs = require("Module:Arguments").getArgs
local yesNo = require("Module:Yesno")
local reagent_card = require('Module:Chemistry').reagent_card
local chem_data = mw.loadJsonData("Module:Chem box/chem data.json")
local current_frame = mw.getCurrentFrame()
-- ====================
local function numeric_table_length(t)
local count = 0
for _ in ipairs(t) do
count = count + 1
end
return count
end
local function table_length(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
local function table_has_value(tab, val)
for _, value in ipairs(tab) do
if value == val then
return true
end
end
return false
end
local function assert_value_not_nil(value, error_message)
if value == nil then
if error_message == nil then
error("value is nil")
else
error(error_message)
end
end
end
-- Makes the first letter uppercase.
-- Source: https://stackoverflow.com/a/2421746
local function capitalize(str)
return (str:gsub("^%l", string.upper))
end
local function passthrough_assert_true(value, valueToReturnIfTrue, errorMessageOnFalse)
if value then
return valueToReturnIfTrue
else
error(errorMessageOnFalse)
end
end
local function find_first_numeric_table_item_matching_condition(table, condition)
for i, item in ipairs(table) do
if condition(item, i, table) then
return item
end
end
end
-- table concat function because ofcourse fucking table.concat doesn't work
local function concat_numberic_table(tbl, sep)
local temp_table = {}
for i = 1, numeric_table_length(tbl) do
table.insert(temp_table, tbl[i])
end
return table.concat(temp_table, sep)
end
local function ternary_strict(valueToCheck, valueIfTrue, valueIfFalse)
if valueToCheck == true then
return valueIfTrue
else
return valueIfFalse
end
end
local function table_map(tbl, mapFn)
local res = {}
for k, v in pairs(tbl) do
table.insert(res, mapFn(v, k, tbl))
end
return res
end
local function num_table_reduce(tbl, reduceFn, initial_value)
local out = initial_value
for i, v in ipairs(tbl) do
out = reduceFn(out, v, i, tbl)
end
return out
end
local function table_filter(tbl, filterFn)
local out = {}
for k, v in pairs(tbl) do
if filterFn(v, k, tbl) then out[k] = v end
end
return out
end
-- Sorts a table into a new table.
-- An alternative to cases such as when table.sort is not working,
-- like when trying to sort JSON tables (thanks fuckass lua).
local function table_to_sorted(tbl, sortfn)
local keys = {}
for key, _ in pairs(tbl) do
table.insert(keys, key)
end
table.sort(keys, function(keyA, keyB) return sortfn(tbl[keyA], tbl[keyB]) end)
local t2 = {}
for _, key in ipairs(keys) do
table.insert(t2, tbl[key])
end
return t2
end
-- ====================
-- Lookups reagent by ID or name.
--
-- Raises an error if no reagent was found.
-- Set `no_error` to `true` to return `nil` instead.
function p.lookup_reagent(query, no_error)
local query_lower = string.lower(query)
for _, reagent in ipairs(chem_data) do
assert_value_not_nil(reagent.id)
assert_value_not_nil(reagent.name)
if reagent.id == query then
return reagent
end
if string.lower(reagent.name) == query_lower then
return reagent
end
end
return passthrough_assert_true(no_error, nil, "failed to lookup reagent: no match was found")
end
local function get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order, a, b)
return function (a, b)
return map_of_reagent_name_or_id_to_order[string.lower(a.name or a.id)]
< map_of_reagent_name_or_id_to_order[string.lower(b.name or b.id)]
end
end
-- Sorts an array of reagents in place by name (or ID if no name), a to z.
-- @param reagents Reagents to sort.
-- @param to_sorted Whether to create a new sorted table instead of sorting in place.
local function sort_reagents_az(reagents, to_sorted)
local reagents_names_or_ids = table_map(reagents, function (reagent)
return string.lower(reagent.name or reagent.id)
end)
table.sort(reagents_names_or_ids)
local map_of_reagent_name_or_id_to_order = num_table_reduce(reagents_names_or_ids, function (accum, value, i)
accum[value] = i
return accum
end, {})
if to_sorted then
return table_to_sorted(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order))
else
table.sort(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order))
end
end
-- ====================
function p.generate_chem_box(frame)
local args = getArgs(frame)
local query = args[1]
assert_value_not_nil(query, "failed to generate chem box: query was not provided")
-- ===================
local reagent = p.lookup_reagent(query)
assert_value_not_nil(reagent.color)
assert_value_not_nil(reagent.name)
assert_value_not_nil(reagent.recipes)
assert_value_not_nil(reagent.effectStrings)
assert_value_not_nil(reagent.description)
assert_value_not_nil(reagent.physicalDescription)
assert_value_not_nil(reagent.textColorTheme)
local recipes_container_el = mw.html.create("div")
if numeric_table_length(reagent.recipes) == 0 then
local no_recipes_text_el = mw.html.create("span"):css("color", "gray"):node("No recipes")
recipes_container_el:node(no_recipes_text_el)
else
for _, recipe in ipairs(reagent.recipes) do
assert_value_not_nil(recipe.id)
assert_value_not_nil(recipe.reactants)
assert_value_not_nil(recipe.products)
local recipe_box_template_args = {}
recipe_box_template_args.name = recipe.id
-- a list of catalist reactants.
-- contains "id" and "amount"
local catalyst_reactants = {}
for reactant_i, reactant in ipairs(recipe.reactants) do
local reactant_name = capitalize(reactant.name)
local reactant_amount = reactant.count
local reactant_is_catalyst = reactant.isCatalyst or false
local label = reactant_name
local link = reactant.id
if reactant_is_catalyst then
table.insert(catalyst_reactants, {
name = reactant_name,
id = reactant_id,
amount = reactant_amount,
})
end
local component_el = current_frame:expandTemplate({
title = "Recipe Component",
args = {
item = label,
amount = reactant_amount,
itemlink = link
-- TODO
-- image = ""
},
})
recipe_box_template_args["component-" .. reactant_i] = component_el
end
local temperature_args = {}
if recipe.minTemp ~= nil or recipe.maxTemp ~= nil then
local temperature_string = ""
if recipe.minTemp ~= nil then
temperature_string = tostring(recipe.minTemp) .. "K "
end
temperature_string = temperature_string .. "<"
if recipe.maxTemp ~= nil then
temperature_string = temperature_string .. " " .. tostring(recipe.maxTemp) .. "K"
end
temperature_args = {temperature = temperature_string}
end
local required_mixer = "Beaker"
if recipe.requiredMixerCategories ~= nil then
required_mixer = recipe.requiredMixerCategories[1]
end
recipe_box_template_args.transformer = current_frame:expandTemplate({
title = required_mixer,
args = temperature_args
})
for product_i, product in ipairs(recipe.products) do
local component_el = current_frame:expandTemplate({
title = "Recipe Component",
args = {
item = capitalize(product.name),
amount = product.count,
-- TODO
-- image = ""
},
})
recipe_box_template_args["result-" .. product_i] = component_el
end
-- add catalysts as products, if any
for catalyst_product_i, catalyst_product_entry in ipairs(catalyst_reactants) do
local product_name = catalyst_product_entry.name
local product_amount = catalyst_product_entry.amount
local label = current_frame:expandTemplate({
title = "Tooltip",
args = {
'<sup class="quickbox-catalyst-note">+</sup>',
'<span class="quickbox-catalyst-note-tooltip">This product is a catalyst reactant, remaining after the reaction.</span>',
},
}) .. product_name
local component_el = current_frame:expandTemplate({
title = "Recipe Component",
args = {
item = label,
amount = product_amount,
-- TODO
-- image = ""
},
})
local index = numeric_table_length(recipe.products) + catalyst_product_i
recipe_box_template_args["result-" .. index] = component_el
end
local recipes_el = current_frame:expandTemplate({
title = "Recipe Box",
args = recipe_box_template_args,
})
recipes_container_el:node(recipes_el)
end
end
local effects = current_frame:preprocess(concat_numberic_table(reagent.effectStrings, ""))
return reagent_card {
id = reagent.id,
color = reagent.color,
name = capitalize(reagent.name),
recipes = tostring(recipes_container_el:allDone()),
metabolisms = effects,
desc = reagent.description,
physicalDesc = reagent.physicalDescription,
}
end
function p.generate_chem_boxes_for_all_reagents()
local container_el = mw.html.create("div"):addClass("reagents-list")
-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort
local reagents_sorted = sort_reagents_az(chem_data, true)
for _, reagent in ipairs(reagents_sorted) do
assert_value_not_nil(reagent.id)
container_el:node(p.generate_chem_box({
reagent.id,
}))
end
return container_el:allDone()
end
function p.generate_chem_boxes_for_group(frame)
local args = getArgs(frame)
local query = args[1]
assert_value_not_nil(query)
-- ========
local query_lc = string.lower(query)
local container_el = mw.html.create("div"):addClass("reagents-list")
local reagents = table_filter(chem_data, function (reagent)
return reagent.group
and string.lower(reagent.group) == query_lc
end)
-- sort to a new obj cuz chem data is a json loaded table and is not enumerable by table.sort,
-- even when filtered smh
reagents = sort_reagents_az(reagents, true)
for _, reagent in ipairs(reagents) do
container_el:node(p.generate_chem_box({
reagent.id,
}))
end
return container_el:allDone()
end
return p