Module:Item util: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
(Cargo's implementation of HOLDS breaks when there is a properly escaped quotation mark at the end of a string literal. Instead, we avoid using HOLDS by explicitly joining the child table and then comparing using a native operator.)
(Use new error object from Module:Util. Return the error object instead of the output string and let the calling module decide what to do with it.)
 
(3 intermediate revisions by the same user not shown)
Line 23: Line 23:


local m = {}
local m = {}
function m.format_value(tpl_args, value, options)
    -- value: table
    --  min:
    --  max:
    -- options: table
    --  func: Function to transform the value retrieved from the database
    --  fmt: Format string (or function that returns format string) to use for the value. Default: '%s'
    --  fmt_range: Format string to use for the value range. Default: '(%s-%s)'
    --  color: poe_color code to use for the value range. False for no color. Default: 'mod'
    --  class: Additional css class added to color tag
    --  inline: Format string to use for the output
    --  inline_color: poe_color code to use for the output. False for no color. Default: 'default'
    --  inline_class: Additional css class added to inline color tag
    --  no_color: (Deprecated; use color=false instead)
    --  return_color: (Deprecated; returns both value.out and value without this)
    if options.color ~= false and options.no_color == nil then
        local base = {
            min = value.base_min or value.base,
            max = value.base_max or value.base,
        }
        if options.color then
            value.color = options.color
        elseif value.min ~= base.min or value.max ~= base.max then
            value.color = 'mod'
        else
            value.color = 'value'
        end
    end
    if options.func then
        value.min = options.func(tpl_args, value.min)
        value.max = options.func(tpl_args, value.max)
    end
    options.fmt = options.fmt or '%s'
    if type(options.fmt) == 'function' then -- Function that returns the format string
        options.fmt = options.fmt(tpl_args, value)
    end
    if value.min == value.max then -- Static value
        value.out = string.format(options.fmt, value.min)
    else -- Range value
        options.fmt_range = options.fmt_range or i18n.range
        value.out = string.format(
            string.format(options.fmt_range, options.fmt, options.fmt),
            value.min,
            value.max
        )
    end
    if value.color then
        value.out = m_util.html.poe_color(value.color, value.out, options.class)
    end
    if type(options.inline) == 'function' then
        options.inline = options.inline(tpl_args, value)
    end
    if options.inline and options.inline ~= '' then
        value.out = string.format(options.inline, value.out)
        if options.inline_color ~= false then
            options.inline_color = options.inline_color or 'default'
            value.out = m_util.html.poe_color(options.inline_color, value.out, options.inline_class)
        end
    end
    local return_color = options.return_color and value.color or nil
    if return_color then
        return value.out, return_color
    end
    return value.out, value
end


function m.get_item_namespaces(args)
function m.get_item_namespaces(args)
Line 176: Line 110:
     -- Append to join, where and orderBy clauses
     -- Append to join, where and orderBy clauses
     if qargs.join then
     if qargs.join then
         query.join = table.concat({query.join, qargs.join}, ', ')
        -- 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
     end
     if qargs.where then
     if qargs.where then
         query.where = table.concat({query.where, qargs.where}, ' AND ')
         query.where = table.concat(m_util.table.merge({query.where, qargs.where}), ' AND ')
     end
     end
     if qargs.orderBy then
     if qargs.orderBy then
         query.orderBy = table.concat({qargs.orderBy, query.orderBy}, ', ')
         query.orderBy = table.concat(m_util.table.merge({qargs.orderBy, query.orderBy}), ', ')
     end
     end
     results = m_cargo.query(tables, fields, query)
     results = m_cargo.query(tables, fields, query)
Line 188: Line 124:
     if #results == 0 then
     if #results == 0 then
         -- No results found
         -- No results found
         err = m_util.misc.raise_error_or_return{
         err = m_util.Error{
             raise_required = true,
             message = string.format(
            args = args,
            msg = string.format(
                 i18n.errors.no_results_found,
                 i18n.errors.no_results_found,
                 search_param,
                 search_param,
                 args[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
     elseif #results > 1 then
         -- More than one result found
         -- More than one result found
Line 214: Line 151:
         end
         end
         if (map_count == 0 or map_count ~= #results) and drop_enabled_count ~= 1 then
         if (map_count == 0 or map_count ~= #results) and drop_enabled_count ~= 1 then
             err = m_util.misc.raise_error_or_return{
             err = m_util.Error{
                 raise_required = true,
                 message = string.format(
                args = args,
                msg = string.format(
                     i18n.errors.many_results_found,
                     i18n.errors.many_results_found,
                     search_param,
                     search_param,
                     args[search_param]
                     args[search_param]
                 )
                 ),
             }
                code = 'many_results_found',
                issue = args.issue_all_errors or false,
                category = i18n.categories.failed_query,
             }:throw()
         end
         end
     end
     end
Line 228: Line 166:
         mw.logObject(results)
         mw.logObject(results)
     end
     end
     if err ~= nil then
     if err then
        if not m_util.cast.boolean(args.nocat) then
            err = err .. m_util.misc.add_category({args.error_category or i18n.categories.failed_query})
        end
         return {error = err}
         return {error = err}
     end
     end

Latest revision as of 19:36, 13 December 2022

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