Module:Elderfield/Utility
Jump to navigation
Jump to search
-- Module:Elderfield/Utility
--
-- Shared helpers for every Elderfield module: safe data access, formatting, and
-- link building. Written defensively — a missing id must degrade to readable text,
-- never to a script error on a reader's page.
local p = {}
--- Loads a data module, returning an empty table if it does not exist yet.
-- @param name string domain name, e.g. "Items"
-- @return table
function p.data(name)
local ok, data = pcall(mw.loadData, 'Module:Elderfield/Data/' .. name)
if ok and type(data) == 'table' then return data end
return {}
end
--- Looks one record up by id.
function p.get(domain, id)
if id == nil or id == '' then return nil end
return p.data(domain)[tostring(id)]
end
--- Trims a template argument, mapping blank to nil.
function p.arg(frame, name, default)
local args = frame.args
local v = args[name]
if v == nil and frame.getParent then
local parent = frame:getParent()
if parent then v = parent.args[name] end
end
if v == nil then return default end
v = mw.text.trim(v)
if v == '' then return default end
return v
end
--- A wiki link to a page title, falling back to plain text.
function p.link(title, label)
if not title or title == '' then return label or '' end
if label and label ~= title then
return '[[' .. title .. '|' .. label .. ']]'
end
return '[[' .. title .. ']]'
end
--- Formats a currency amount using the wiki's gold template when present.
function p.gold(n)
if not n then return '' end
return mw.getCurrentFrame():expandTemplate{ title = 'Price', args = { n } }
end
--- Renders an item icon by IconSet index.
function p.icon(index, size)
if not index or index == 0 then return '' end
-- A cell of the shared sheet (IconSet.png, 16 columns of 32px), cut out the way
-- Template:Icon does it: the sheet as a picture, in a box one icon wide.
local s = tonumber(size) or 24
local i = tonumber(index) or 0
return string.format("<span class=\"elderfield-icon\" aria-hidden=\"true\" style=\"display:inline-block;vertical-align:middle;position:relative;overflow:hidden;width:%dpx;height:%dpx;image-rendering:pixelated\"><span style=\"position:absolute;width:512px;line-height:0;left:0;top:0;transform-origin:0 0;transform:scale(%s) translateX(-%dpx) translateY(-%dpx)\">[[File:IconSet.png|link=|alt=]]</span></span>",
s, s, tostring(s / 32), (i % 16) * 32, math.floor(i / 16) * 32)
end
--- How many entries a list holds. A table from mw.loadData is a read-only proxy that answers
-- 0 to #, so a data module's list measured with # looked empty: every drop table, gift table
-- and quality ladder drawn from one printed nothing. ipairs sees through the proxy.
function p.count(list)
local n = 0
for _ in ipairs(list or {}) do n = n + 1 end
return n
end
--- Joins a list of strings, dropping blanks.
function p.join(list, sep)
local out = {}
for _, v in ipairs(list or {}) do
if v ~= nil and v ~= '' then out[#out + 1] = tostring(v) end
end
return table.concat(out, sep or ', ')
end
--- Builds a sortable wikitable. Existing string cells and fixed-width headers
-- retain their original output. Structured cells optionally supply text/sortKey/style;
-- headers may supply sortType/style/cellStyle; options may supply a table-local style.
local function attribute(value)
return tostring(value):gsub('&', '&'):gsub('"', '"'):gsub('<', '<'):gsub('>', '>')
end
function p.table(headers, rows, class, options)
if p.count(rows) == 0 then return '' end
local price = require('Module:Elderfield/Price')
local fixed = false
for _, h in ipairs(headers) do if h.width then fixed = true end end
local open = '{| class="wikitable elderfield-table sortable ' .. (class or '') .. '"'
local tableStyle = fixed and 'width:100%; table-layout:fixed' or ''
if options and options.style then tableStyle = tableStyle .. (tableStyle ~= '' and '; ' or '') .. options.style end
if tableStyle ~= '' then open = open .. ' style="' .. attribute(tableStyle) .. '"' end
local buf, head = { open }, {}
for _, h in ipairs(headers) do
local style, attrs = {}, {}
if h.width then style[#style + 1] = 'width:' .. h.width end
if h.align == 'right' then style[#style + 1] = 'text-align:right' end
if h.style then style[#style + 1] = h.style end
if price.isField(h.label) then style[#style + 1] = 'text-align:center' end
if #style > 0 then attrs[#attrs + 1] = 'style="' .. attribute(table.concat(style, '; ')) .. '"' end
if h.sortType then attrs[#attrs + 1] = 'data-sort-type="' .. attribute(h.sortType) .. '"' end
head[#head + 1] = #attrs > 0 and ('! ' .. table.concat(attrs, ' ') .. ' | ' .. h.label) or ('! ' .. h.label)
end
buf[#buf + 1] = table.concat(head, '\n')
for _, row in ipairs(rows) do
buf[#buf + 1] = '|-'
for i, cell in ipairs(row) do
local h, style, attrs = headers[i], {}, {}
local structured = type(cell) == 'table'
local text = structured and (cell.text or '') or (cell or '')
if h and h.align == 'right' then style[#style + 1] = 'text-align:right' end
if h and h.cellStyle then style[#style + 1] = h.cellStyle end
if structured and cell.style then style[#style + 1] = cell.style end
if h and price.isField(h.label) then
style[#style + 1] = 'text-align:center'
text = price.format(text)
end
if #style > 0 then attrs[#attrs + 1] = 'style="' .. attribute(table.concat(style, '; ')) .. '"' end
if structured and cell.sortKey ~= nil then attrs[#attrs + 1] = 'data-sort-value="' .. attribute(cell.sortKey) .. '"' end
buf[#buf + 1] = #attrs > 0 and ('| ' .. table.concat(attrs, ' ') .. ' | ' .. text) or ('| ' .. text)
end
end
buf[#buf + 1] = '|}'
return table.concat(buf, '\n')
end
--- Stored text as a page should print it. Descriptions carry the game's icons as {{Icon}}
-- calls, and what a module returns is not expanded again, so it is expanded here, and only
-- when there is a template in it.
function p.text(frame, s)
if s == nil then return '' end
s = tostring(s)
if frame and frame.preprocess and s:find("{{", 1, true) then return frame:preprocess(s) end
return s
end
--- A visible, categorised error rather than a silent blank.
function p.missing(what, id)
return '<span class="elderfield-missing" style="color:#c4614f;border-bottom:1px dotted #c4614f;cursor:help">' ..
mw.text.nowiki(what .. ' ' .. tostring(id) .. ' is not in the wiki data') ..
'</span>[[Category:Pages with missing Elderfield data]]'
end
return p