Module:Item util

From Path of Exile 2 Wiki
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]


This is a meta module.

This module is meant to be used only by other modules. It should not be invoked in wikitext.

Lua logo

This module depends on the following other modules:

This meta module provides utility functions for modules that deal with items.

Usage

This module should be loaded with require().

-------------------------------------------------------------------------------
-- 
--                              Module:Item util
-- 
-- This meta module contains utility functions for modules that deal with items
-------------------------------------------------------------------------------

local m_util = require('Module:Util')
local m_cargo -- Lazy load require('Module:Cargo')

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Item util')

-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Item util/config/sandbox') or mw.loadData('Module:Item util/config')

local i18n = cfg.i18n

-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local m = {}

function m.get_item_namespaces(args)
    -- Returns item namespaces from config as a table or as a comma-separated string
    args.format = args.format or 'table'
    if args.format == 'list' then
        return cfg.item_namespaces_list
    end
    return cfg.item_namespaces
end

function m.query_item(args, qargs)
    qargs = qargs or {}
    m_cargo = m_cargo or require('Module:Cargo')
    if not m_util.table.has_any_key(args, cfg.search_terms) then
        error(i18n.errors.missing_search_term)
    end
    local tables = {'items'}
    local fields = {
        'items._pageName=_pageName',
        'items.drop_enabled=drop_enabled',
        'items.class_id=class_id',
    }
    local query = {
        groupBy = 'items._pageID',
        orderBy = 'items.drop_enabled DESC',
    }
    local search_param
    local results
    if args.metadata_id then
        query.where = string.format(
            'items.metadata_id = "%s"',
            args.metadata_id
        )
        search_param = 'metadata_id'
    elseif args.page then
        -- Join with _pageData in order to check for page redirect
        tables[#tables+1] = '_pageData'
        query.join = 'items._pageName = _pageData._pageNameOrRedirect'
        query.where = string.format(
            '_pageData._pageName = "%s"',
            m_cargo.addslashes(args.page)
        )
        search_param = 'page'
    else
        tables[#tables+1] = 'maps'
        tables[#tables+1] = 'map_series'
        query.join = 'items._pageID = maps._pageID, maps.series = map_series.name'
        query.where = string.format(
            'items._pageNamespace IN (%s)',
            m.get_item_namespaces{format = 'list'}
        )
        query.orderBy = 'map_series.ordinal DESC, ' .. query.orderBy
        if args.item_name_exact then
            query.where = query.where .. string.format(
                ' AND items.name = "%s"',
                m_cargo.addslashes(args.item_name_exact)
            )
            search_param = 'item_name_exact'
        else
            --[[
            Cargo's implementation of HOLDS breaks when there is a properly 
            escaped quotation mark at the end of a string literal.

            Example of a WHERE clause that results in an error:
                items.name_list HOLDS "\"O\' Eternal\""

            Instead, we avoid using HOLDS by explicitly joining the child table 
            and then comparing using a native operator.
            --]]
            tables[#tables+1] = 'items__name_list'
            query.join = query.join .. ', items._ID = items__name_list._rowID'
            query.where = query.where .. string.format(
                ' AND items__name_list._value = "%s"',
                m_cargo.addslashes(args.item_name)
            )
            search_param = 'item_name'
        end
    end
    -- Append additional tables and fields
    if type(qargs.tables) == 'table' and #qargs.tables > 0 then
        tables = m_util.table.merge(tables, qargs.tables)
    end
    if type(qargs.fields) == 'table' and #qargs.fields > 0 then
        fields = m_util.table.merge(fields, qargs.fields)
    end
    -- Append to join, where and orderBy clauses
    if qargs.join then
        -- m_util.table.merge rebuilds the table, which removes empty values
        -- TODO: Use a better function than m_util.table.merge
        query.join = table.concat(m_util.table.merge({query.join, qargs.join}), ', ')
    end
    if qargs.where then
        query.where = table.concat(m_util.table.merge({query.where, qargs.where}), ' AND ')
    end
    if qargs.orderBy then
        query.orderBy = table.concat(m_util.table.merge({qargs.orderBy, query.orderBy}), ', ')
    end
    results = m_cargo.query(tables, fields, query)
    local err
    if #results == 0 then
        -- No results found
        err = m_util.Error{
            message = string.format(
                i18n.errors.no_results_found,
                search_param,
                args[search_param]
            ),
            code = 'no_results_found',
            issue = args.issue_all_errors or false,
            category = i18n.categories.failed_query,
        }:throw()
    elseif #results > 1 then
        -- More than one result found
        -- 
        -- If results are all maps, use the one from the most recent map
        -- series, regardless of whether it's drop enabled. Otherwise, 
        -- if only one of the results is drop enabled then use that one.
        local map_count = 0
        local drop_enabled_count = 0
        for i, v in ipairs(results) do
            if v.class_id == 'Map' then
                map_count = map_count + 1
            end
            if m_util.cast.boolean(v.drop_enabled) then
                drop_enabled_count = drop_enabled_count + 1
            end
        end
        if (map_count == 0 or map_count ~= #results) and drop_enabled_count ~= 1 then
            err = m_util.Error{
                message = string.format(
                    i18n.errors.many_results_found,
                    search_param,
                    args[search_param]
                ),
                code = 'many_results_found',
                issue = args.issue_all_errors or false,
                category = i18n.categories.failed_query,
            }:throw()
        end
    end
    if args.debug then
        mw.logObject(results)
    end
    if err then
        return {error = err}
    end
    return results[1] -- orderBy ensures that the first result is the one we want
end

return m