Module:Recipes: Difference between revisions
Jump to navigation
Jump to search
(Completely rewrite. About 5x - 10x faster for big tables; added some new features. (Documentation needs to be updated later)) |
(fixed a bug about |ingredients=) |
||
Line 413: | Line 413: | ||
local group = getItemGroupName(v) |
local group = getItemGroupName(v) |
||
if group then |
if group then |
||
pieces[i] = "ingredients HOLDS LIKE '%¦" .. escape(group) .. "¦%'" |
pieces[i] = pieces[i] .. " OR ingredients HOLDS LIKE '%¦" .. escape(group) .. "¦%'" |
||
end |
end |
||
end |
end |
Revision as of 14:58, 17 April 2023
Documentation The documentation below is transcluded from Module:Recipes/doc. (edit | history)
This module provides the functionality of Template:Recipes. See that template's page for documentation.
------- l10n info --------------
local l10n_info = mw.loadData('Module:Recipes/l10n')
------- The following is not related to l10n. --------------
local item_link = require('Module:Item').go
local trim = mw.text.trim
local cargo = mw.ext.cargo
local tag = mw.text.tag
local cache = require 'mw.ext.LuaCache'
local currentFrame -- global cache for current frame object.
local inputArgs -- global args cache.
local lang -- cache current lang.
local l10n_table
-- The order in which all columns are displayed is:
-- col-As • Result • col-Bs • Ingredients • col-Cs • station-col-befores • Crafting Station • station-col-afters • Col-Ds
local extCols_A = 0
local extCols_B = 0
local extCols_C = 0
local extCols_D = 0
local extCols_stationBefore = 0
local extCols_stationAfter = 0
local options = {
--resultanchor = nil,
result_order = 'result',
needCate = 1,
linkResult = true,
showResultId = false,
needGroup = true,
--withStation = true,
--stationGroup = withStation,
--resultTemplate = nil
}
local initOptions = function(args)
options.resultanchor = getArg('resultanchor')
local _result_order = getArg('orderbyid')
if _result_order == 'y' or _result_order == 'yes' then
options.result_order = 'resultid'
end
local _cate = getArg('cate')
if _cate == 'force' or _cate == 'all' then
options.needCate = 2
elseif _cate == 'n' or _cate == 'no' then
options.needCate = nil
end
local _link = getArg('link')
if _link == 'n' or _link == 'no' then
options.linkResult = false
end
if getArg('showresultid') then
options.showResultId = true
end
options.withStation = not getArg('nostation')
options.stationGroup = options.withStation
if options.withStation then
local grouping = getArg('stationgrouping')
if grouping == 'n' or grouping == 'no' then
options.stationGroup = false
end
end
local _needGroup = getArg('grouping')
if _needGroup == 'n' or _needGroup == 'no' then
options.needGroup = false
end
options.resultTemplate = getArg('resulttemplate')
end
-- credit: http://richard.warburton.it
-- this version is with trim.
local explode = function(div,str)
if (div=='') then return false end
local pos,arr = 0,{}
-- for each divider found
for st,sp in function() return string.find(str,div,pos,true) end do
table.insert(arr, trim(string.sub(str,pos,st-1))) -- Attach chars left of current divider
pos = sp + 1 -- Jump past current divider
end
table.insert(arr, trim(string.sub(str,pos))) -- Attach chars right of last divider
return arr
end
local l10n = function(key)
return l10n_table[key] or l10n_info['en'][key]
end
function getArg(key)
local v = trim(inputArgs[key] or '')
if v=='' then
return nil
else
return v
end
end
local tr = (function()
local cache = {}
return function(text, lang, link)
local key = lang..'|'..text
if link then
key = key .. '|l'
end
local str = cache[key]
if not str then
str = currentFrame:expandTemplate{ title = 'tr', args = {text, lang=lang, link=link}}
cache[key] = str
end
return str
end
end)()
local itemLink = (function()
local cache = {}
return function(name, args)
local key = name.."|"
if args then
for k, v in pairs(args) do
key = key..k..'='..tostring(v)..'|'
end
end
if not cache[key] then
local args = args and mw.clone(args) or {}
local pos = string.find(name, '#', 1, true)
if pos then
-- extract ext args from name (<itemname>(#i:<image>)?(#n:<note>)?)
for k, v in string.gmatch(name, "#(%l):([^#]+)") do
if k == 'i' then
args['image'] = v
elseif k == 'n' then
args['note2'] = v
end
end
name = string.sub(name, 1, pos-1)
end
args[1] = name
if (not args[2]) or args[2]=='' then
args[2] = tr(name, lang)
end
args['small'] = 'y'
args['lang'] = lang or 'en'
args['nolink'] = args['nolink'] and 'y' or nil
cache[key] = item_link(currentFrame, args)
end
return cache[key]
end
end)()
local getVersionIconsStr = (function()
local local_cache = {}
return function(version)
local key = lang..':recipes:versionicon:'..version
-- {{version icons}} is a slow template, so cache its result:
local vstr = local_cache[key] or cache.get(key) -- cache for current lang
if not vstr then
vstr = currentFrame:expandTemplate{ title = 'version icons', args = {version} }
cache.set(key, vstr, 3600*24) -- cache 24hr.
local_cache[key] = vstr
end
return vstr
end
end)()
-- for xxx/yyy, return a array of itemname, split xxx/yyy to item1=xxx, item2=yyy.
-- If it's something like "Lead/Iron Bar", it will normalize as item1 = Iron Bar, item2 = Lead Bar.
-- for single name, return nil.
local split = (function()
local metals = {
['Copper/Tin'] = 1,
['Silver/Tungsten'] = 1,
['Gold/Platinum'] = 1,
['Iron/Lead'] = 1,
['Cobalt/Palladium'] = 1,
['Mythril/Orichalcum'] = 1,
['Adamantite/Titanium'] = 1,
['Tin/Copper'] = 2,
['Tungsten/Silver'] = 2,
['Platinum/Gold'] = 2,
['Lead/Iron'] = 2,
['Palladium/Cobalt'] = 2,
['Orichalcum/Mythril'] = 2,
['Titanium/Adamantite'] = 2,
}
return function(name)
local count = select(2, name:gsub("/", "/", 2))
if count == 0 then
-- only 1 item
return nil
elseif count == 1 then
-- 2 items
local item1a, item1b, item2a, item2b = name:match("^%s*(%S+)%s*(.-)/%s*(%S+)%s*(.-)$")
local x = metals[item1a..'/'..item2a]
if tostring(item1b) == '' and x then
item1b = item2b
end
if x == 2 then
return {trim(item2a..' '..item2b), trim(item1a..' '..item1b)}
else
return {trim(item1a..' '..item1b), trim(item2a..' '..item2b)}
end
else
-- 3 or more items
return explode('/', name)
end
end
end)()
-- return 1 or 2 value(s), when input is name[note], return item, note.
local itemname = function(str)
local item, note = str:match("^(.-)(%b[])$")
if item then
return item, note
else
return str
end
end
-- normalize ingredient name input, Lead Bar=>¦Lead Bar¦, Iron/Lead Bar => ¦Iron Bar¦Lead Bar¦, Lead/Iron Bar => ¦Iron Bar¦Lead Bar¦ ....
local normalize = function(name)
local list = split(name)
if not list then
return '¦' .. itemname(name) .. '¦'
end
local result = {''}
for _, v in ipairs(list) do
result[#result+1] = itemname(v)
end
result[#result+1] = ''
return table.concat(result, '¦')
end
local escape = function(str)
return str:gsub("'", "\\'"):gsub("'", "\\'")
end
local enclose = function(str)
return "'" .. escape(str) .. "'"
end
local getItemGroupName = (function()
local groupIndexInfo = {
['Wood'] = 1, ['Ebonwood'] = 1, ['Rich Mahogany'] = 1, ['Pearlwood'] = 1, ['Shadewood'] = 1,
['Spooky Wood'] = 1, ['Boreal Wood'] = 1, ['Palm Wood'] = 1, ['Ash Wood'] = 1,
['Iron Bar'] = 2, ['Lead Bar'] = 2,
['Sand Block']= 3, ['Pearlsand Block'] = 3, ['Crimsand Block'] = 3, ['Ebonsand Block'] = 3,
['Hardened Sand Block'] = 3, ['Hardened Ebonsand Block'] = 3, ['Hardened Crimsand Block'] = 3, ['Hardened Pearlsand Block'] = 3,
['Red Pressure Plate'] = 4, ['Green Pressure Plate'] = 4, ['Gray Pressure Plate'] = 4, ['Brown Pressure Plate'] = 4,
['Blue Pressure Plate'] = 4, ['Yellow Pressure Plate'] = 4, ['Lihzahrd Pressure Plate'] = 4,
['Bird'] = 5, ['Blue Jay'] = 5, ['Cardinal'] = 5,
['Black Scorpion'] = 6,['Scorpion'] = 6,
['Squirrel'] = 7,['Red Squirrel'] = 7,
['Grubby'] = 8,['Sluggy'] = 8,['Buggy'] = 8,
['Mallard Duck'] = 9, ['Duck'] = 9,
['Sulphur Butterfly'] = 10, ['Julia Butterfly'] = 10, ['Monarch Butterfly'] = 10, ['Purple Emperor Butterfly'] = 10,
['Red Admiral Butterfly'] = 10, ['Tree Nymph Butterfly'] = 10, ['Ulysses Butterfly'] = 10, ['Zebra Swallowtail Butterfly'] = 10,
['Firefly'] = 11, ['Lightning Bug'] = 11,
['Snail'] = 12,['Glowing Snail'] = 12,
['Apple'] = 13, ['Apricot'] = 13, ['Banana'] = 13, ['Blackcurrant'] = 13, ['Blood Orange'] = 13, ['Cherry'] = 13, ['Coconut'] = 13,
['Dragon Fruit'] = 13, ['Elderberry'] = 13, ['Grapefruit'] = 13, ['Lemon'] = 13, ['Mango'] = 13, ['Peach'] = 13, ['Pineapple'] = 13,
['Plum'] = 13, ['Rambutan'] = 13, ['Star Fruit'] = 13, ['Spicy Pepper'] = 13, ['Pomegranate'] = 13,
['Black Dragonfly'] = 14, ['Blue Dragonfly'] = 14, ['Green Dragonfly'] = 14, ['Orange Dragonfly']= 14,
['Red Dragonfly'] = 14, ['Yellow Dragonfly'] = 14,
['Turtle'] = 15, ['Jungle Turtle'] = 15,
['Scarlet Macaw'] = 16, ['Blue Macaw'] = 16,
['Yellow Cockatiel'] = 17, ['Gray Cockatiel'] = 17,
['Cloud in a Balloon'] = 18, ['Blue Horseshoe Balloon'] = 18,
['Blizzard in a Balloon'] = 19, ['White Horseshoe Balloon'] = 19,
['Sandstorm in a Balloon'] = 20, ['Yellow Horseshoe Balloon'] = 20,
['Silly Green Balloon'] = 21, ['Silly Pink Balloon'] = 21, ['Silly Purple Balloon'] = 21,
['Guide to Critter Companionship'] = 22,['Guide to Critter Companionship (Inactive)'] = 22,
['Guide to Environmental Preservation'] = 23,['Guide to Environmental Preservation (Inactive)'] = 23,
}
local groupIndex = {
[1] = 'Any Wood',
[2] = 'Any Iron Bar',
[3] = 'Any Sand',
[4] = 'Any Pressure Plate',
[5] = 'Any Bird',
[6] = 'Any Scorpion',
[7] = 'Any Squirrel',
[8] = 'Any Jungle Bug',
[9] = 'Any Duck',
[10] = 'Any Butterfly',
[11] = 'Any Firefly',
[12] = 'Any Snail',
[13] = 'Any Fruit',
[14] = 'Any Dragonfly',
[15] = 'Any Turtle',
[16] = 'Any Macaw',
[17] = 'Any Cockatiel',
[18] = 'Any Cloud Balloon',
[19] = 'Any Blizzard Balloon',
[20] = 'Any Sandstorm Balloon',
[21] = 'Any Balloon',
[22] = 'Any Guide to Critter Companionship',
[23] = 'Any Guide to Environmental Preservation'
}
return function(item)
return groupIndexInfo[item] and groupIndex[groupIndexInfo[item]]
end
end)()
local normalizeStation = function(station)
if station == 'Altar' then
station = 'Demon Altar'
end
return station
end
local normalizeVersion = function(_version)
if not _version or _version == '' then
return ''
end
_version = trim(_version):lower()
local version = ''
if _version:find('desktop', 1, true) then
version = version .. ' desktop'
end
if _version:find('console', 1, true) then
version = version .. ' console'
end
if _version:find('old-gen', 1, true) then
version = version .. ' old-gen'
end
if _version:find('japan', 1, true) then
version = version .. ' japan'
end
if _version:find('mobile', 1, true) then
version = version .. ' mobile'
end
if _version:find('3ds', 1, true) then
version = version .. ' 3ds'
end
if _version:find('switch', 1, true) then
version = version .. ' switch'
end
if _version:find('tmodloader', 1, true) then
version = version .. ' tmodloader'
end
if version == ' desktop console old-gen mobile 3ds switch tmodloader' or version == '' then
return ''
end
return trim(version)
end
local criStr = function(args)
local constraints = {}
-- station = ? and station != ?
local _station = trim(args['station'] or '')
local _stationnot = trim(args['stationnot'] or '')
if _station ~= '' then
local pieces = explode('/', _station)
for i, v in ipairs(pieces) do
pieces[i] = "station = " .. enclose(normalizeStation(v))
end
constraints[#constraints+1] = table.concat(pieces, ' OR ')
end
if _stationnot ~= '' then
local pieces = explode('/', _stationnot)
for i, v in ipairs(pieces) do
pieces[i] = 'station <> ' .. enclose(normalizeStation(v))
end
constraints[#constraints+1] = table.concat(pieces, ' AND ')
end
-- result = ? and result != ?
local _result = trim(args['result'] or '')
local _resultnot = trim(args['resultnot'] or '')
if _result ~= '' then
local pieces = explode('/', _result)
for i, v in ipairs(pieces) do
if mw.ustring.sub(v, 1, 5) == 'LIKE ' then
pieces[i] = 'result LIKE ' .. enclose(trim(mw.ustring.sub(v, 6)))
else
pieces[i] = 'result=' .. enclose(v)
end
end
constraints[#constraints+1] = table.concat(pieces, ' OR ')
end
if _resultnot ~= '' then
local pieces = explode('/', _resultnot)
for i, v in ipairs(pieces) do
if mw.ustring.sub(v, 1, 5) == 'LIKE ' then
pieces[i] = 'result NOT LIKE ' .. enclose(trim(mw.ustring.sub(v, 6)))
else
pieces[i] = 'result <> ' .. enclose(v)
end
end
constraints[#constraints+1] = table.concat(pieces, ' AND ')
end
-- ingredient = ?
local _ingredient = trim(args['ingredient'] or '')
if _ingredient ~= '' then
local pieces = explode('/', _ingredient)
for i, v in ipairs(pieces) do
if mw.ustring.sub(v, 1, 1) == '#' then
pieces[i] = "ingredients HOLDS LIKE '%¦" .. escape(mw.ustring.sub(v, 2)) .. "¦%'"
elseif mw.ustring.sub(v, 1, 5) == 'LIKE ' then
pieces[i] = "ingredients HOLDS LIKE '%¦" .. escape(trim(mw.ustring.sub(v, 6))) .. "¦%'"
else
pieces[i] = "ingredients HOLDS LIKE '%¦" .. escape(v) .. "¦%'"
-- any xxx
local group = getItemGroupName(v)
if group then
pieces[i] = pieces[i] .. " OR ingredients HOLDS LIKE '%¦" .. escape(group) .. "¦%'"
end
end
end
constraints[#constraints+1] = table.concat(pieces, ' OR ')
end
--versions
local _version = normalizeVersion(args['version'] or args['versions'] or '')
if _version ~= '' then
constraints[#constraints+1] = 'version = ' .. enclose(_version)
end
local where = ''
if constraints[1] then
where = '('.. table.concat(constraints, ') AND (') .. ')'
end
return where
end
local resultCell = function(row)
local result, resultid, resultimage, resultnote, amount, version = row['result'], row['resultid'], row['resultimage'], (row['resulttext'] or ''), row['amount'], (row['version'] or '')
local str
--version text
if version ~= '' then
str = {'<div class="version-note note-text small">', l10n('version_note_before'), getVersionIconsStr(version), l10n('version_note_after'), '</div>'}
else
str = {}
end
local args = {anchor = options.resultanchor, nolink = not options.linkResult, class='multi-line'}
if options.showResultId then
args['id'] = resultid
end
if resultimage then
args['image'] = resultimage
end
str[#str+1] = itemLink(result, args)
if amount ~= '1' then
str[#str+1] = tag('span', {class='am'}, amount)
end
-- note text
if resultnote ~= '' then
local note_l10n = l10n('result_note') or {}
resultnote = note_l10n[resultnote] or resultnote
str[#str+1] = tag('div', {class="result-note note-text small"}, resultnote)
end
str = table.concat(str)
if options.resultTemplate then
local template_str = currentFrame:expandTemplate{ title = options.resultTemplate, args = {
link = options.linkResult, showid = options.showResultId,
resultid=resultid, resultimage=resultimage, resultnote=resultnote,
result=result, amount=amount, versions=version,
} }
str = template_str:gsub('@@@@', str)
end
return str
end
local ingredientsCell = function(ingredientsArgs, itemLinkArgs)
local str = ''
local rows = explode('^', ingredientsArgs)
for i, v in ipairs(rows) do
local item, amount = v:match('^(.-)¦(.-)$')
local items = split(item)
if not items then
items = itemLink(item, itemLinkArgs)
else
for j, itemname in ipairs(items) do
items[j] = itemLink(itemname, itemLinkArgs)
end
items = table.concat(items, l10n('ingredients_sep'))
end
if amount ~= '1' then
items = items .. tag('span', {class="am"}, amount)
end
rows[i] = tag('li', nil, items)
end
return tag('ul', nil, table.concat(rows))
end
local stationLink = function(station, options, andStr, orStr, orStrInner)
local il = function(s, o)
if o then
for k,v in pairs(o) do options[k] = v end
end
return itemLink(s, options)
end
local ectoMist = function() return il('Ecto Mist', {mode = 'text'}) end
local water = function() return '<span class="ib">' .. il('Water') .. orStrInner .. il('Sink') .. '</span>' end
if station == 'By Hand' then
return l10n('station_by_hand')
elseif station == 'Furnace' or station == 'Work Bench' or station == 'Sawmill' or station == "Tinkerer's Workshop" or station == 'Dye Vat'
or station == 'Loom' or station == 'Keg' or station == 'Hellforge' or station == 'Bookcase' or station == 'Imbuing Station' or station == 'Lava'
or station == 'Honey' or station == 'Glass Kiln' or station == 'Flesh Cloning Vat' or station == 'Autohammer' or station == 'Crystal Ball'
or station == 'Ice Machine' or station == 'Meat Grinder' or station == 'Living Loom' or station == 'Heavy Work Bench' or station == 'Sky Mill'
or station == 'Solidifier' or station == 'Honey Dispenser' or station == 'Bone Welder' or station == 'Blend-O-Matic' or station == 'Steampunk Boiler'
or station == 'Ancient Manipulator' or station == 'Lihzahrd Furnace' or station == 'Living Wood' or station == 'Decay Chamber' or station == 'Teapot'
or station == 'Campfire' then
return il(station)
elseif station == 'Iron Anvil' then
return il('Iron Anvil') .. orStr .. il('Lead Anvil')
elseif station == 'Iron Anvil and Ecto Mist' then
return '<span class="ib">' .. il('Iron Anvil') .. orStrInner .. il('Lead Anvil') .. '</span>' .. andStr.. ectoMist()
elseif station == 'Adamantite Forge' then
return il('Adamantite Forge') .. orStr .. il('Titanium Forge')
elseif station == 'Mythril Anvil' then
return il('Mythril Anvil') .. orStr .. il('Orichalcum Anvil')
elseif station == 'Demon Altar' then
return il('Demon Altar') .. orStr .. il('Crimson Altar')
elseif station == 'Cooking Pot' then
return il('Cooking Pot') .. orStr .. il('Cauldron')
elseif station == 'Placed Bottle' then
return il('Placed Bottle') .. orStr .. il('Alchemy Table')
elseif station == 'Water' then
return il('Water') .. orStr .. il('Sink')
elseif station == 'Table and Chair' then
return il('Table') .. andStr .. il('Chair')
elseif station == 'Work Bench and Chair' then
return il('Work Bench') .. andStr .. il('Chair')
elseif station == 'Crystal Ball and Lava' then
return il('Crystal Ball') .. andStr .. il('Lava')
elseif station == 'Crystal Ball and Honey' then
return il('Crystal Ball') .. andStr .. il('Honey')
elseif station == 'Crystal Ball and Water' then
return il('Crystal Ball') .. andStr.. water()
elseif station == 'Sky Mill and Water' then
return il('Sky Mill') .. andStr.. water()
elseif station == 'Sky Mill and Snow Biome' then
return il('Sky Mill') .. andStr.. l10n('snow_biome')
elseif station == 'Placed Bottle only' then
return il('Placed Bottle')
elseif station == 'Heavy Work Bench and Ecto Mist' then
return il('Heavy Work Bench') .. andStr.. ectoMist()
elseif station == 'Work Bench and Ecto Mist' then
return il('Work Bench') .. andStr.. ectoMist()
elseif station == "Tinkerer's Workshop and Ecto Mist" then
return il("Tinkerer's Workshop") .. andStr.. ectoMist()
elseif station == 'Loom and Ecto Mist' then
return il('Loom') .. andStr.. ectoMist()
elseif station == 'Shimmer' then
return il('Shimmer', {[2] = l10n('station_shimmer')})
elseif station == 'Chlorophyte Extractinator' then
return il('Chlorophyte Extractinator', {[2] = l10n('station_chlorophyte_extractinator')})
elseif station == 'Bone Welder and Ecto Mist' then
return il('Bone Welder') .. andStr .. ectoMist()
else
return station
end
end
local stationCell = function(station, inline)
return stationLink(station, inline and {} or {wrap = 'y'}, l10n('station_sep_and'), l10n('station_sep_or'), l10n('station_sep_or'))
end
-- for extract.
local compactStation = function(station, formatString)
if station == 'By Hand' then
return
end
return formatString:gsub('@@@@', stationLink(station, {mode = 'image'}, " & ", " / ", " / "))
end
local addCate, cateStr = (function()
local cateCache = {}
local addCate = function(station)
cateCache[station] = true
end
local cateStr = function()
local str = ''
for station, _ in pairs(cateCache) do
str = str .. '[[Category:'..(l10n('station_cate')[station] or tr(station, lang, 'y'))..']]'
end
if str ~= '' then
str = '[[Category:'.. l10n('cate_craftable').. ']]' .. str
end
return str
end
return addCate, cateStr
end)()
local tableStart = function(caption)
local buffer = {}
--helper function
local getExtColsInfo = function(field_prefix, htmlClass)
for i = 1, math.huge do
local v = getArg(field_prefix .. i)
if not v then
return i - 1
end
buffer[#buffer+1] = tag('th', {class = htmlClass}, v)
i = i + 1
end
end --helper function
local attr = {
class = 'terraria cellborder recipes ',
id = getArg('id'),
style = getArg('css') or getArg('style'),
}
local sortable = getArg('sortable')
if not (sortable == 'n' or sortable == 'no') then
attr.class = attr.class .. 'sortable '
end
local class = getArg('class')
if class then
attr.class = attr.class .. class
end
extCols_A = getExtColsInfo('col-A-')
buffer[#buffer+1] = tag('th', {class="result"}, (getArg('header-result') or l10n('header_result')) )
extCols_B = getExtColsInfo('col-B-')
buffer[#buffer+1] = tag('th', {class="ingredients"}, (getArg('header-ingredients') or l10n('header_ingredients')) )
extCols_C = getExtColsInfo('col-C-')
if options.withStation then
extCols_stationBefore = getExtColsInfo('station-col-before-', 'station')
buffer[#buffer+1] = tag('th', {class='station'}, (getArg('header-station') or l10n('header_station')) )
extCols_stationAfter = getExtColsInfo('station-col-after-', 'station')
end
extCols_D = getExtColsInfo('col-D-')
str = tag('tr', nil, table.concat(buffer))
return attr, (caption and {tag('caption', nil, caption), str} or {str})
end
local tableRow = function(row, index, status)
local resultIndex = getArg('result-index-#'..index)
or getArg('result-index-'..row.result..'-'..(row.version or ''))
or getArg('result-index-'..row.result)
local result = table.concat({
row.result, (row.resultid or ''), (row.resultimage or ''),
(row.resulttext or ''), row.amount, (row.version or '')
}, '|')
local stationIndex -- used later
if status.station ~= row.station then
status.station = row.station
status.stationCounterPrev = status.stationCounter
status.stationCounter = 1
else
status.stationCounter = status.stationCounter + 1
end
if status.result ~= result then
status.result = result
status.resultCounterPrev = status.resultCounter
status.resultCounter = 1
else
status.resultCounter = status.resultCounter + 1
end
if status.resultIndex ~= resultIndex then
status.resultIndex = resultIndex
status.resultIndexCounterPrev = status.resultIndexCounter
status.resultIndexCounter = 1
else
status.resultIndexCounter = status.resultIndexCounter + 1
end
local buffer = {}
local extColCell = function(col, class, index, counter, needGroup, placeholder)
local content
local attr = {class = class}
if index then
local cell = index..'-row-'..col
content = getArg(cell) or ''
attr.colspan = getArg(cell..'-colspan')
else
content = ''
end
if counter == 1 and needGroup then
attr.rowspan = placeholder
end
buffer[#buffer+1] = tag('td', attr, content)
end
local extColCells = function(count, prefix, station)
if not station and (options.needGroup and status.resultIndexCounter > 1) then
return
end
for i = 1, count do
local col = prefix .. i
if station then
extColCell(col, 'station '..col, stationIndex, status.stationCounter, options.stationGroup, 'xxxrowspanxxx')
else
extColCell(col, col, resultIndex, status.resultIndexCounter, options.needGroup, 'zzzrowspanzzz')
end
end
end
extColCells(extCols_A, 'col-A-')
if not options.needGroup or status.resultCounter == 1 then
local attr = { class="result", ['data-sort-value'] = row.result }
if status.resultCounter == 1 and options.needGroup then
attr.rowspan = "yyyrowspanyyy"
end
buffer[#buffer+1] = tag('td', attr, resultCell(row))
end
extColCells(extCols_B, 'col-B-')
buffer[#buffer+1] = tag('td', {class="ingredients"}, ingredientsCell(row.args))
extColCells(extCols_C, 'col-C-')
if options.withStation and (not options.stationGroup or status.stationCounter == 1) then
station_index = getArg('station-index-'..row.station)
extColCells(extCols_stationBefore, 'station-col-before-', true)
buffer[#buffer+1] = tag('td', {class="station", rowspan = options.stationGroup and "xxxrowspanxxx" or nil}, stationCell(row.station))
extColCells(extCols_stationAfter, 'station-col-after-', true)
end
extColCells(extCols_D, 'col-D-')
return tag('tr', {['data-rowid']=index}, table.concat(buffer))
end
local extRows = function(isTop)
local fieldPrefix = isTop and 'topextrow-' or 'extrow-'
local prefix
local buffer, notEmpty
local extColCell = function(col, class)
local cell = prefix..col
local content = getArg(cell)
notEmpty = notEmpty or content
buffer[#buffer+1] = tag('td', {class = class or col, colspan = getArg(cell..'-colspan'), rowspan = getArg(cell..'-rowspan')}, content or '')
end
local extColCells = function(count, colPrefix, station)
for i = 1, count do
local col = colPrefix .. i
extColCell(col, station and ('station ' .. col) )
end
end
local rows = {}
for i = 1, math.huge do
buffer = {}
notEmpty = false
prefix = fieldPrefix..i..'-'
extColCells(extCols_A, 'col-A-')
extColCell('col-result', 'result')
extColCells(extCols_B, 'col-B-')
extColCell('col-ingredients', 'ingredients')
extColCells(extCols_C, 'col-C-')
if options.withStation then
extColCells(extCols_stationBefore, 'station-col-before-', true)
extColCell('col-station', 'station')
extColCells(extCols_stationAfter, 'station-col-after-', true)
end
extColCells(extCols_D, 'col-D-')
if notEmpty then
rows[#rows+1] = tag('tr', {['data-'..fieldPrefix..'id']=i}, table.concat(buffer))
else
return table.concat(rows)
end
end
end
local tableBody = function(result, needGroup, rootpagename, caption, expectedrows)
local attr, buffer = tableStart(caption)
-- top ext rows:
buffer[#buffer+1] = extRows(true)
-- main rows:
local tableRows = {}
local status = {
station = nil,
stationCounter = 0,
stationCounterPrev = 0,
result = nil,
resultCounter = 0,
resultCounterPrev = 0,
resultIndex = nil,
resultIndexCounter = 0,
resultIndexCounterPrev = 0,
}
for i, row in ipairs(result) do
tableRows[i] = tableRow(row, i, status)
if status.stationCounter == 1 and options.stationGroup and i > 1 then
tableRows[i-status.stationCounterPrev] = tableRows[i-status.stationCounterPrev]:gsub("xxxrowspanxxx", status.stationCounterPrev)
end
if status.resultCounter == 1 and options.needGroup and i > 1 then
tableRows[i-status.resultCounterPrev] = tableRows[i-status.resultCounterPrev]:gsub("yyyrowspanyyy", status.resultCounterPrev)
end
if status.resultIndexCounter == 1 and options.needGroup and i > 1 then
tableRows[i-status.resultIndexCounterPrev] = tableRows[i-status.resultIndexCounterPrev]:gsub("zzzrowspanzzz", status.resultIndexCounterPrev)
end
-- cate:
if options.needCate and (options.needCate == 2 or rootpagename == tr(row['result'], lang)) then
addCate(row['station'])
end
end
-- rowspan for last groups
if options.stationGroup then
tableRows[#tableRows-status.stationCounter+1] = tableRows[#tableRows-status.stationCounter+1]:gsub("xxxrowspanxxx", status.stationCounter)
end
if options.needGroup then
tableRows[#tableRows-status.resultCounter+1] = tableRows[#tableRows-status.resultCounter+1]:gsub("yyyrowspanyyy", status.resultCounter)
tableRows[#tableRows-status.resultIndexCounter+1] = tableRows[#tableRows-status.resultIndexCounter+1]:gsub("zzzrowspanzzz", status.resultIndexCounter)
end
buffer[#buffer+1] = table.concat(tableRows)
-- ext rows:
buffer[#buffer+1] = extRows()
-- table end
attr['data-expectedRows'] = expectedrows
attr['data-totalRows'] = rows_count
local str = tag('table', attr, table.concat(buffer))
if expectedrows and rows_count ~= expectedrows then
str = str .. '[[Category:'.. l10n('cate_unexpected_rows_count') .. ']]'
end
if not expectedrows and rows_count == 0 then
str = str .. '[[Category:'.. l10n('cate_no_row') .. ']]'
end
-- cate
if options.needCate then
str = str .. cateStr()
end
return str
end
----------------------------------------------------------------------------------
local p = {}
-- for {{recipes/register}}
p.register = function(frame)
local args = frame:getParent().args
-- {{{ingredients}}}
local ingredients = {} -- list of {index, itemname, amount}
for k, v in pairs(args) do
if(type(k) == 'number') then
if k % 2 == 1 then -- 2n-1, nth item
local index, item, amount = (k+1)/2, trim(v), trim(args[k+1])
ingredients[index] = {item, amount}
end
end
end
local serialized = '' -- serialized ingredients list
for _, v in ipairs(ingredients) do
serialized = serialized .. '^' .. v[1] .. '¦' .. v[2]
end
table.sort(ingredients, function(a , b) return a[1] < b[1] end) -- sort by ingredient item name
local ingredients_string = ''
local ingredients_string_full = ''
for _, v in ipairs(ingredients) do
local name, amount = unpack(v)
local ingstr = normalize(name)
ingredients_string = ingredients_string .. '^' .. ingstr
ingredients_string_full = ingredients_string_full .. '^' .. ingstr .. amount
end
--{{{version}}}, normalize
version = normalizeVersion(args['version'])
--store
local sub = mw.ustring.sub
frame:callParserFunction('#cargo_store:_table=Recipes',{
result = trim(args['result'] or ''),
resultid = trim(args['resultid'] or ''),
resultimage = trim(args['image'] or ''),
resulttext = trim(args['note'] or ''), -- reuse the legacy "resulttext" field as note.
amount = trim(args['amount'] or ''),
version = version,
station = normalizeStation(trim(args['station'] or '')),
ingredients = sub(ingredients_string, 2),
ings = sub(ingredients_string_full, 2),
args = sub(serialized, 2),
})
end -- p.register
-- for {{recipes}}
p.query = function(frame)
currentFrame = frame -- global frame cache
inputArgs = frame:getParent().args -- global input args cache
lang = frame.args['lang'] or 'en'
l10n_table = l10n_info[lang] or l10n_info['en']
initOptions(inputArgs)
local where = trim(inputArgs['where'] or '')
if where == '' then
where = criStr(inputArgs)
end
-- no constraint no result.
if where == '' then
return frame:expandTemplate{ title='error', args={ "Recipes: No constraint", from = 'Recipes', inline = 'y'}}
end
-- format:
local _title = getArg('title')
local _expectedrows = trim(inputArgs['expectedrows'] or '')
if _expectedrows ~= '' then
_expectedrows = tonumber(_expectedrows)
else
_expectedrows = nil
end
local rootpagename = mw.title.getCurrentTitle().rootText
local orderBy = options.result_order .. ", amount DESC, version"
if options.withStation then
orderBy = "station, " .. orderBy -- order by station first for station grouping.
end
-- query
local result = mw.ext.cargo.query('Recipes', 'result, resultid, resultimage, resulttext, amount, version, station, args', {
where = where,
groupBy = "resultid, result, amount, ings, version, station",
orderBy = orderBy,
limit = 2000,
})
-- format
return tableBody(result, needGroup, rootpagename, _title, _expectedrows)
end -- p.query
-- for {{recipes/extract}}
p.extract = function(frame)
currentFrame = frame -- global frame cache
inputArgs = frame:getParent().args -- global input args cache
lang = frame.args['lang'] or 'en'
l10n_table = l10n_info[lang] or l10n_info['en']
local where = trim(inputArgs['where'] or '')
if where == '' then
where = criStr(inputArgs)
end
-- no constraint no result.
if where == '' then
return frame:expandTemplate{ title='error', args={ "Recipes/extract: no constraint", from = 'Recipes', inline = 'y'}}
end
local result_order = trim(inputArgs['orderbyid'] or '')
if result_order == 'y' or result_order == 'yes' then
result_order = 'resultid'
else
result_order = 'result'
end
-- query:
local result = mw.ext.cargo.query('Recipes', 'result, resultid, resultimage, resulttext, amount, version, station, args', {
where = where,
groupBy = "resultid, result, amount, version, ings",
orderBy = result_order .. ", amount DESC, version", -- Don't order by station
limit = 20, -- enough.
})
local handles = {}
handles.compact = function()
--default mode = compact
local withVersion = not getArg('noversion')
local withNote = not getArg('nonote')
local withOne = getArg('full')
local withResult = getArg('withresult')
local ResultFirst = getArg('resultfirst')
local withStation = not getArg('nostation')
local rows = {}
for index, row in ipairs(result) do
local result
if withResult then
local args = {mode='image'}
if row['resultimage'] then
args['image'] = row['resultimage']
end
result = itemLink(row['result'], args)
if row['amount'] ~= '1' or withOne then
result = row['amount']..' '.. result
end
if withNote and (row['resulttext'] or '') ~= '' then
result = result .. tag('span', {class="result-note"}, row['resulttext'])
end
end
local ingList = explode('^', row['args'])
local args = {mode='image'}
for i, v in ipairs(ingList) do
local item, amount = v:match('^(.-)¦(.-)$')
local items = split(item)
if not items then
item = itemLink(item, args)
else
for j, itemname in ipairs(items) do
items[j] = itemLink(itemname, args)
end
item = table.concat(items, " / ")
end
if amount ~= '1' or withOne then
ingList[i] = amount .. ' ' .. item
else
ingList[i] = item
end
end
ingList = table.concat(ingList, ' + ')
local station
if withStation then
station = compactStation(row['station'], getArg('formatStation') or l10n('compact_format_station'))
end
-- format
local formatString = getArg('format')
if not formatString then
if (withStation and station) then
if withResult then
formatString = l10n('compact_format_recipeWithResultAndStation')
else
formatString = l10n('compact_format_recipeWithStation')
end
else
if withResult then
formatString = l10n('compact_format_recipeWithResult')
end
end
end
local recipe
if formatString then
local replacement = { ['@station@'] = station, ['@ingredient@'] = ingList, ['@result@'] = result }
recipe = formatString:gsub('@.-@', function(s) return replacement[s] or s end)
else
recipe = ingList
end
if withVersion and (row['version'] or '') ~= '' then
local version = getVersionIconsStr(row['version'])
formatString = getArg('formatVersion') or l10n('compact_format_WithVersion')
local replacement = { ['@version@'] = version, ['@recipe@'] = recipe }
recipe = formatString:gsub('@.-@', function(s) return replacement[s] or s end)
end
rows[index] = tag('span', {class="recipe"}, recipe)
end
local sep = getArg('sep') or getArg('seperator') or l10n('compact_default_sep')
if sep == 'null' or sep == 'nil' then
sep = ''
end
return tag('span',{class='recipes extract compact'}, table.concat(rows, sep))
end --handles.compact
handles.ingredients = function()
local withVersion = not getArg('noversion')
local args = getArg('noversionicon') and {icons = 'n'}
local rows = {}
for i, row in ipairs(result) do
local ingredient = ingredientsCell(row['args'], args)
if withVersion and (row['version'] or '') ~= '' then
local version = getVersionIconsStr(row['version'])
local formatString = getArg('formatVersion') or l10n('ingredients_format_WithVersion')
local replacement = { ['@version@'] = version, ['@ingredient@'] = ingredient }
ingredient = formatString:gsub('@.-@', function(s) return replacement[s] or s end)
end
rows[i] = tag('div', {class='ingredients'}, ingredient)
end
local sep = getArg('sep') or getArg('seperator') or l10n('default_sep_ingredients')
if sep == 'null' or sep == 'nil' then
sep = ''
end
return tag('div', {class='recipes extract ingredients'}, table.concat(rows, sep))
end --handles.ingredients
handles.station = function ()
-- only return first row.
return result[1] and tag('span',{class='recipes extract staion'}, stationCell(result[1]['station'], true))
end --handles.station
handles.result = function ()
-- only return first row.
if not result[1] then
return
end
local row = result[1]
local result, resultid, resultimage, resultnote, amount, version = row['result'], row['resultid'], row['resultimage'], (row['resulttext'] or ''), row['amount'], (row['version'] or '')
--options
local linkResult = true
local _link = getArg('link')
if _link == 'n' or _link == 'no' then
linkResult = false
end
showResultId = getArg('showresultid') and true or false
local withOne = getArg('full')
local withNote = not getArg('nonote')
local withVersion = not getArg('noversion')
-- result item first:
local args = {nolink = not linkResult, class='multi-line'}
if showResultId then
args['id'] = resultid
end
if resultimage then
args['image'] = resultimage
end
local str = itemLink(result, args)
-- amount
if amount ~= '1' or withOne then
local formatString = getArg('format') or l10n('result_format_WithAmount')
local replacement = { ['@result@'] = str, ['@amount@'] = amount }
str = formatString:gsub('@.-@', function(s) return replacement[s] or s end)
end
-- note text
if resultnote ~= '' and withNote then
local note_l10n = l10n('result_note') or {}
resultnote = note_l10n[resultnote] or resultnote
str = str .. tag('span', {class="result-note note-text small"}, resultnote)
end
-- version text
if withVersion and version ~= '' then
local formatString = getArg('formatVersion') or l10n('result_format_WithVersion')
local replacement = { ['@result@'] = str, ['@version@'] = getVersionIconsStr(version) }
str = formatString:gsub('@.-@', function(s) return replacement[s] or s end)
end
return tag('span', {class='recipes extract result'}, str)
end --handles.result
handles.amount = function()
-- only return first row.
return result[1] and tag('span',{class='recipes extract amount'}, result[1]['amount'])
end --handles.amount
handles.resultamount = handles.amount -- alias
handles['ingredients-buy'] = function()
-- only process first row.
local row = result[1]
local value = 0
local getItemStat = require('Module:Iteminfo').getItemStat
for _, v in ipairs(explode('^', row['args'])) do
local item, amount = v:match('^(.-)¦(.-)$')
value = value + getItemStat( tonumber(currentFrame:expandTemplate{ title = 'itemIdFromName', args = {item, lang='en'} }) or 0, 'value' ) * amount
end
return value
end --handles['ingredients-buy']
handles['ingredients-sell'] = function()
-- only process first row.
local row = result[1]
local value = 0
local getItemStat = require('Module:Iteminfo').getItemStat
for _, v in ipairs(explode('^', row['args'])) do
local item, amount = v:match('^(.-)¦(.-)$')
value = value + math.floor(getItemStat( tonumber(currentFrame:expandTemplate{ title = 'itemIdFromName', args = {item, lang='en'} }) or 0, 'value' )/5) * amount
end
return value
end --handles['ingredients-sell']
-- output:
local mode = getArg('mode') or 'compact'
if handles[mode] then
return (handles[mode])()
else
return frame:expandTemplate{ title='error', args={ "Recipes/extract: Invalid mode", from = 'Recipes', inline = 'y'}}
end
end -- p.extract
-- count; return int; for {{recipes/count}}
p.count = function(frame)
local args = frame:getParent().args
local where = trim(args['where'] or '')
if where == '' then
where = criStr(args)
end
-- no constraint no result.
if where == '' then
return 0
end
-- query: since we must use group by to eliminate duplicates, so we can not use COUNT() to get row count directly. and COUNT(DISTICT ..) will also not get correct result due to HOLD keyword.
local result = mw.ext.cargo.query('Recipes', 'resultid', {
where = where,
groupBy = "resultid, result, amount, ings, version",
limit = 2000,
})
-- count
return #result
end -- p.count
-- return "yes" or ""; for {{recipes/exist}}
p.exist = function(frame)
local args = frame:getParent().args
local where = trim(args['where'] or '')
if where == '' then
where = criStr(args)
end
-- no constraint no result.
if where == '' then
return
end
-- query:
local result = mw.ext.cargo.query('Recipes', 'result', {
where = where,
limit = 1, -- enough.
})
-- output
return result[1] and 'yes'
end -- p.exist
return p