Module:Chem box: Difference between revisions

From Space Station 14 Wiki
Aliser (talk | contribs)
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..."
 
Aliser (talk | contribs)
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('Module:Arguments').getArgs
local getArgs = require("Module:Arguments").getArgs
local yesNo = require('Module:Yesno')
local yesNo = require("Module:Yesno")
local reagent_card = require('Module:Chemistry').reagent_card


local chem_data = mw.loadJsonData("User:UpAndLeaves/chemData.json")
local chem_data = mw.loadJsonData("Module:Chem box/chem data.json")


local current_frame = mw.getCurrentFrame()
local current_frame = mw.getCurrentFrame()
local beaker_el = current_frame.expandTemplate {
    title = 'Beaker'
}


-- ====================
-- ====================


local function numeric_table_length(t)
local function numeric_table_length(t)
    local count = 0
local count = 0
    for _ in ipairs(t) do count = count + 1 end
for _ in ipairs(t) do
    return count
count = count + 1
end
return count
end
end


local function table_length(t)
local function table_length(t)
    local count = 0
local count = 0
    for _ in pairs(t) do count = count + 1 end
for _ in pairs(t) do
    return count
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
for _, value in ipairs(tab) do
        if value == val then
if value == val then
            return true
return true
        end
end
    end
end


    return false
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 value == nil then
        if error_message == nil then
if error_message == nil then
            error("value is nil")
error("value is nil")
        else
else
            error(error_message)
error(error_message)
        end
end
    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))
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
if value then
        return valueToReturnIfTrue
return valueToReturnIfTrue
    else
else
        error(errorMessageOnFalse)
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 find_first_numeric_table_item_matching_condition(table, condition)
local function table_filter(tbl, filterFn)
     for i, item in ipairs(table) do
    local out = {}
         if condition(item, i, table) then
 
            return item
     for k, v in pairs(tbl) do
        end
         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)
    for _, reagent in ipairs(chem_data) do
local query_lower = string.lower(query)
        assert_value_not_nil(reagent.id)
        assert_value_not_nil(reagent.name)


        if reagent.id == query then return reagent end
for _, reagent in ipairs(chem_data) do
        if reagent.name == query then return reagent end
assert_value_not_nil(reagent.id)
    end
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, {})


    return passthrough_assert_true(
if to_sorted then
        no_error,
return table_to_sorted(reagents, get_sort_reagent_az_final_comparator(map_of_reagent_name_or_id_to_order))
        nil,
else
        "failed to lookup reagent: no match was found"
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 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 = {}


    local query = args[1]
for reactant_i, reactant in ipairs(recipe.reactants) do
    assert_value_not_nil(query, "failed to generate chem box: query was not provided")
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


    local reagent = p.lookup_reagent(query)
if reactant_is_catalyst then
    assert_value_not_nil(reagent.color)
table.insert(catalyst_reactants, {
    assert_value_not_nil(reagent.name)
name = reactant_name,
    assert_value_not_nil(reagent.recipes)
id = reactant_id,
    assert_value_not_nil(reagent.effects)
amount = reactant_amount,
    assert_value_not_nil(reagent.desc)
})
    assert_value_not_nil(reagent.physicalDesc)
end


    local recipes_container_el = mw.html.create("div")
local component_el = current_frame:expandTemplate({
    for _, recipe in ipairs(reagent.recipes) do
title = "Recipe Component",
        assert_value_not_nil(recipe.reactants)
args = {
        assert_value_not_nil(recipe.transformer)
item = label,
        assert_value_not_nil(recipe.products)
amount = reactant_amount,
itemlink = link
-- TODO
-- image = ""
},
})


        local recipe_box_template_args = {}
recipe_box_template_args["component-" .. reactant_i] = component_el
        for reactant_i, reactant in ipairs(recipe.reactants) do
end
            local component_el = current_frame.expandTemplate {
                title = 'Recipe Component',
                args = {
                    item = reactant[1],
                    amount = reactant[2]
                    -- TODO
                    -- image = ""
                }
            }


            recipe_box_template_args['component-' .. reactant_i] = component_el
local temperature_args = {}
        end
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
})


        recipe_box_template_args.transformer = beaker_el
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 = ""
},
})


        for _, product in ipairs(recipe.products) do
recipe_box_template_args["result-" .. product_i] = component_el
            local component_el = current_frame.expandTemplate {
end
                title = 'Recipe Component',
                args = {
                    item = product[1],
                    amount = product[2]
                    -- TODO
                    -- image = ""
                }
            }


            recipe_box_template_args['result-' .. product] = component_el
-- add catalysts as products, if any
        end
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 recipes_el = current_frame.expandTemplate {
local label = current_frame:expandTemplate({
            title = 'Recipe Box',
title = "Tooltip",
            args = recipe_box_template_args
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


        recipes_container_el:node(recipes_el)
local component_el = current_frame:expandTemplate({
    end
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 current_frame.expandTemplate {
return container_el:allDone()
        title = 'Manual Chem Box',
        args = {
            color = reagent.color,
            -- textcolor = "",
            name = reagent.name,
            recipes = recipes_container_el:allDone(),
            metabolisms = reagent.effects,
            desc = reagent.desc,
            physicalDesc = reagent.physicalDesc,
        }
    }
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