Module:Item infobox

From Space Station 14 Wiki
Module documentation
View or edit this documentation (about module documentation)

Contains partial implementation for {{Item infobox}}.

Edible by params

Edible by parameters are implemented in this module, entrypoint generate_edible_by.

List of edible by params is defined by edible_by_display_tbl.

Tests

Module:Item infobox/testcases/edible by

✅ All tests passed.

Name Expected Actual
test_empty
test_everyone
test_everyone_except_other_options
test_everyone_except_reptilians
test_moth
test_reptilian

local p = {}
local getArgs = require('Module:Arguments').getArgs
local yesNo = require('Module:Yesno')

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

local edible_by_rest_param_name_lc = "edible by rest"

-- A list of edible by parameters along with the texts to display for each.
-- Param names defined here must be lowercase.
--
-- "edible by rest" is a special case, hence the table format.
-- * If all options are selected, including "edible by rest" (ie food is edible by everyone),
-- then the first form will be used.
-- * If only some options are selected, including "edible by rest",
-- then the second form will be used, following by a list of unselected options.
local edible_by_display_tbl = {
    { param = "edible by reptilians",       display = "Reptilians" },
    { param = "edible by moths",            display = "Moth people" },
    { param = edible_by_rest_param_name_lc, display = { "Everyone", "Everyone, except:" } },
}

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

local function assert_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

local function starts_with(str, substr)
    return string.sub(str, 1, string.len(substr)) == substr
end

local function ends_with(str, substr)
    local substr_length = string.len(substr)
    return string.sub(str, string.len(str) - substr_length + 1, string.len(str)) == substr
end

local function starts_with_insensitive(str, substr)
    return starts_with(string.lower(str), string.lower(substr))
end

local function ends_with_insensitive(str, substr)
    return ends_with(string.lower(str), string.lower(substr))
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

local function table_find(tbl, findFn)
    for k, v in pairs(tbl) do
        if findFn(v, k, tbl) then return v end
    end
end

local function table_contains(tbl, findFn)
    return table_find(tbl, findFn) ~= nil
end

local function table_contains_value(tbl, value)
    return table_find(tbl, function(iter_value)
        return iter_value == value
    end) ~= nil
end

local function table_find_key(tbl, findFn)
    for k, v in pairs(tbl) do
        if findFn(v, k, tbl) then return k end
    end
end

local function table_find_index(tbl, findFn)
    for i, v in ipairs(tbl) do
        if findFn(v, i, tbl) then return i end
    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 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


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

function p.generate_edible_by(frame)
    local args = getArgs(frame)

    local edible_by_args = table_filter(args, function(value, key)
        return starts_with_insensitive(key, "edible by")
    end)

    if table_length(edible_by_args) == 0 then
        return ""
    end

    local edible_by_rest_key = table_find_key(edible_by_args, function(value, key)
        return ends_with_insensitive(key, "rest")
    end)

    -- save the edible by rest value to use later
    local is_edible_by_rest = yesNo(edible_by_args[edible_by_rest_key])

    -- generate a temp table made up only of numerical params to make it sortable.
    -- get rid of unselected "edible by"s.
    local selected_params_lc = {}
    for key, value in pairs(edible_by_args) do
        -- iterate over each key except edible by rest
        if key ~= edible_by_rest_key then
            -- check if its selected
            if yesNo(value) then
                table.insert(selected_params_lc, string.lower(key))
            end
        end
    end

    -- searches for a param name in the params table,
    -- returns the index ("order") of that entry.
    -- throws if no param was found (ie not defined in the table).
    local function find_edible_by_order(param_name_lc)
        local index = table_find_index(edible_by_display_tbl, function(value)
            return value.param == param_name_lc
        end)

        assert_not_nil(index, "failed to find edible by order: param name '" ..
            param_name_lc .. "' if not defined in the edible by params table")

        return index
    end

    -- sort the temp table based on predefine order
    table.sort(selected_params_lc, function(a, b)
        return find_edible_by_order(a) > find_edible_by_order(b)
    end)

    -- render out the params
    local edible_by_rest_entry = is_edible_by_rest
        and table_find(edible_by_display_tbl, function(value)
            return value.param == edible_by_rest_param_name_lc
        end)

    if is_edible_by_rest and edible_by_rest_entry == nil then
        error(
            "failed to generate edible by: edible by rest param is enabled, but it is not defined in the display table")
    end

    local selected_params_lc_len = numeric_table_length(selected_params_lc)
    -- count total selected "edible by" params, including the edible by rest
    local total_selected_params = selected_params_lc_len + (is_edible_by_rest and 1 or 0)
    local display_table_len = numeric_table_length(edible_by_display_tbl)

    local list_el = mw.html.create('ul')

    local function append_li(text)
        list_el
            :tag("li")
            :wikitext(text) -- relates to the li el, not the list el
    end

    local function append_li_and_subli(li_text, subli_els)
        local li_el = mw.html.create("li")
            :wikitext(li_text)

        local subli_ul = mw.html.create('ul')
        for _, node in ipairs(subli_els) do
            subli_ul
                :node(node)
        end

        li_el:
            node(subli_ul)

        list_el
            :node(li_el)
    end

    if total_selected_params == display_table_len then
        -- all params selected, edible by everyone
        -- edible by rest is assumed to be on implicitly, cz otherwise the count won't match.
        append_li(edible_by_rest_entry.display[1])
    else
        if is_edible_by_rest then
            -- if edible by contains the rest option, we compile a list of those excluded from the display table.
            -- those selected are excluded from the compiled list.

            -- compile a list of those potentially excluded
            local excluded_pool = table_filter(edible_by_display_tbl, function(entry)
                return entry.param ~= edible_by_rest_param_name_lc
            end)

            -- find whose excluded (not selected)
            local excluded = table_filter(excluded_pool, function(entry)
                return not table_contains_value(selected_params_lc, entry.param)
            end)

            -- format em as li els
            local excluded_as_li_els = table_map(excluded, function(value)
                return mw.html.create("li")
                    :wikitext(value.display)
            end)

            append_li_and_subli(edible_by_rest_entry.display[2], excluded_as_li_els)
        else
            -- if edible by doesn't contain the rest option, then its a regular list

            for _, param_name_lc in ipairs(selected_params_lc) do
                local entry = table_find(edible_by_display_tbl, function(entry)
                    return entry.param == param_name_lc
                end)

                assert_not_nil(entry, "failed to generate edible by param: param '" ..
                    param_name_lc .. "' (lowercase) is not defined in the edible by display table")

                append_li(entry.display)
            end
        end
    end

    return tostring(list_el)
end

return p