Module:Item util: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
(Added function for formatting values – based on function from Module:Util)
(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.)
 
(6 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 148: Line 82:
             search_param = 'item_name_exact'
             search_param = 'item_name_exact'
         else
         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(
             query.where = query.where .. string.format(
                 ' AND items.name_list HOLDS "%s"',
                 ' AND items__name_list._value = "%s"',
                 m_cargo.addslashes(args.item_name)
                 m_cargo.addslashes(args.item_name)
             )
             )
Line 164: 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 176: 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 194: Line 143:
         local drop_enabled_count = 0
         local drop_enabled_count = 0
         for i, v in ipairs(results) do
         for i, v in ipairs(results) do
             if v['class_id'] == 'Map' then
             if v.class_id == 'Map' then
                 map_count = map_count + 1
                 map_count = map_count + 1
             end
             end
             if m_util.cast.boolean(v['items.drop_enabled']) then
             if m_util.cast.boolean(v.drop_enabled) then
                 drop_enabled_count = drop_enabled_count + 1
                 drop_enabled_count = drop_enabled_count + 1
             end
             end
         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
     if err ~= nil then
     if args.debug then
        if not m_util.cast.boolean(args.nocat) then
        mw.logObject(results)
            err = err .. m_util.misc.add_category({args.error_category or i18n.categories.failed_query})
    end
        end
    if err then
         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