Module:Recipes

--- l10n info -- local l10n_info = mw.loadData('Module:Recipes/l10n') local lang -- cache current lang. local l10n_table

--- 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 resultanchor

-- The order in which all columns are displayed is: -- col-A • Result • col-B • Ingredients • col-C • station-col-before • Crafting Station • station-col-after • Col-D 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) local _resultanchor = getArg('resultanchor') or 'y' -- the default is 'y'	if _resultanchor == 'n' or _resultanchor == 'no' then options.resultanchor = nil else options.resultanchor = _resultanchor end

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				-- (#i: )?(#n: )?				local item = string.sub(name, 1, pos-1)				for k, v in string.gmatch(name, "#(%l):([^#]+)") do					if k == 'i' then						-- #i:old is a shortcut for #i:& (old).png						if(v == 'old') then							args['image'] = item .. ' (old).png'						else							args['image'] = string.gsub(v, '&', item)						end					elseif k == 'n' then						args['note2'] = v					end				end				name = item			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 --			args['anchor'] = args['anchor'] 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		-- 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 an 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,		['Iron/Lead'] = 1,		['Silver/Tungsten'] = 1,		['Gold/Platinum'] = 1,		['Cobalt/Palladium'] = 1,		['Mythril/Orichalcum'] = 1,		['Adamantite/Titanium'] = 1,		-- the same ones as above but in reversed order		['Tin/Copper'] = 2,		['Lead/Iron'] = 2,		['Tungsten/Silver'] = 2,		['Platinum/Gold'] = 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)

-- normalize ingredient name input: -- * Lead Bar=>¦Lead Bar¦, Iron/Lead Bar => ¦Iron Bar¦Lead Bar¦, Lead/Iron Bar => ¦Iron Bar¦Lead Bar¦ -- * strip #i: and #n: local normalize = function(name) local list = split(name) local strip = function(str) return string.gsub(str, '#.+', '') end if not list then return '¦' .. strip(name) .. '¦'	end local result = {''} for _, v in ipairs(list) do		result[#result+1] = strip(v) end result[#result+1] = '' return table.concat(result, '¦') end

local escape = function(str) return str:gsub("'", "\\'"):gsub("&#39;", "\\'") end local enclose = function(str) return "'" .. escape(str) .. "'" end

local getItemGroupName = (function	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'	}	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, }	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 = ''

-- First check Japan; if returns true, skip all the others, same as we do with the other exclusivity templates if _version:find('japan', 1, true) then version = version .. ' japan' elseif _version:find('newchinese', 1, true) then version = version .. ' newchinese' else if _version:find('desktop', 1, true) or _version:find('pc', 1, true) then version = version .. ' pc' end if _version:find('console', 1, true) then version = version .. ' console' end if _version:find('mobile', 1, true) then version = version .. ' mobile' end if _version:find('old-gen', 1, true) then version = version .. ' old-gen' end if _version:find('windowsphone', 1, true) then version = version .. ' windowsphone' end if _version:find('3ds', 1, true) then version = version .. ' 3ds' end if _version:find('tmodloader', 1, true) then version = version .. ' tmodloader' end if _version:find('tmodloaderlegacy', 1, true) then version = version .. ' tmodloaderlegacy' end

-- If every version returns true, don't display any icons if version == ' pc console mobile old-gen windowsphone 3ds tmodloader tmodloaderlegacy' or version == '' then return '' end 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 -- args for result local args = {anchor = options.resultanchor, nolink = not options.linkResult, class='multi-line'} --version text if version ~= '' then str = {' ', l10n('version_note_before'), getVersionIconsStr(version), l10n('version_note_after'), ' '} args['icons'] = 'no' --If there is version-exclusive info, don't show ecions for result. else str = {} end 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 ' ' .. il('Water') .. orStrInner .. il('Sink') .. ' ' end if station == 'By Hand' then return l10n('station_by_hand') elseif station == 'Ancient Manipulator' or station == 'Autohammer' or station == 'Blend-O-Matic' or station == 'Bone Welder' or station == 'Bookcase' or station == 'Campfire' or station == 'Crystal Ball' or station == 'Decay Chamber' or station == 'Dye Vat' or station == 'Flesh Cloning Vat' or station == 'Furnace' or station == 'Glass Kiln' or station == 'Heavy Work Bench' or station == 'Hellforge' or station == 'Honey' or station == 'Honey Dispenser' or station == 'Ice Machine' or station == 'Imbuing Station' or station == 'Keg' or station == 'Lava' or station == 'Living Loom' or station == 'Lihzahrd Furnace' or station == 'Living Wood' or station == 'Loom' or station == 'Meat Grinder' or station == 'Sawmill' or station == 'Sky Mill' or station == 'Solidifier' or station == 'Steampunk Boiler' or station == 'Teapot' or station == "Tinkerer's Workshop" or station == 'Work Bench' then return il(station) -- altars elseif station == 'Demon Altar' then return il('Demon Altar') .. orStr .. il('Crimson Altar') -- anvils elseif station == 'Iron Anvil' then return il('Iron Anvil') .. orStr .. il('Lead Anvil') elseif station == 'Mythril Anvil' then return il('Mythril Anvil') .. orStr .. il('Orichalcum Anvil') -- forges elseif station == 'Adamantite Forge' then return il('Adamantite Forge') .. orStr .. il('Titanium Forge') -- the rest should be more or less alphabetized 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 == 'Placed Bottle only' then return il('Placed Bottle') 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 == 'Water' then return il('Water') .. orStr .. il('Sink') -- combos 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' or station == 'Sky Mill and Snow biome' then return il('Sky Mill') .. andStr.. il('Snow biome', {mode = 'text'}) 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') -- Ecto Mist elseif station == 'Iron Anvil and Ecto Mist' then return ' ' .. il('Iron Anvil') .. orStrInner .. il('Lead Anvil') .. ' ' .. andStr.. ectoMist elseif station == 'Bone Welder and Ecto Mist' then return il('Bone Welder') .. andStr .. ectoMist elseif station == 'Heavy Work Bench and Ecto Mist' then return il('Heavy Work Bench') .. andStr.. ectoMist elseif station == 'Loom and Ecto Mist' then return il('Loom') .. andStr.. ectoMist elseif station == "Tinkerer's Workshop and Ecto Mist" then return il("Tinkerer's Workshop") .. andStr.. ectoMist elseif station == 'Work Bench and Ecto Mist' then return il('Work Bench') .. 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'}, "&thinsp;&amp;&thinsp;", "&thinsp;/&thinsp;", "&hairsp;/&hairsp;")) 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 .. 		end		if str ~=  then			str =  .. 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 tableRows[1] then -- table has rows. 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 end buffer[#buffer+1] = table.concat(tableRows) -- ext rows: buffer[#buffer+1] = extRows -- table end local rows_count = #tableRows 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 .. ''	end if not expectedrows and rows_count == 0 then str = str .. ''	end -- cate if options.needCate then str = str .. cateStr end return str end

--

local p = {}

-- for p.register = function(frame) local args = frame:getParent.args

-- 	local ingArgs = {} -- full input info, used in building ingredients cells. local ingredients = {} -- ingredient itemnames, for query. local ings = {} -- used in "group by" to eliminate duplicate rows. local index = 1 while args[index*2-1] do		local itemStr, amount = trim(args[index*2-1]), trim(args[index*2]) local item = normalize(itemStr) -- e.g. ¦Iron Bar¦Lead Bar¦ ingredients[index] = item ings[index] = item..amount ingArgs[index] = itemStr..'¦'..amount index = index + 1 end table.sort(ings)

--, normalize version = normalizeVersion(args['version'])

--store 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 = table.concat(ingredients, '^'),		ings = table.concat(ings, '^'),		args = table.concat(ingArgs, '^'),	}) end -- p.register

-- for 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 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 raw = getArg('raw') 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 -- note text if withNote and (row.resulttext or ) ~=  then local note_l10n = l10n('result_note') or {} resultnote = note_l10n[row.resulttext] or row.resulttext result = result .. tag('span', {class="result-note note-text small"}, resultnote) 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 if not raw then item = itemLink(item, args) end else for j, itemname in ipairs(items) do						if not raw then items[j] = itemLink(itemname, args) else items[j] = item end end item = table.concat(items, "&hairsp;/&hairsp;") end if amount ~= '1' or withOne then ingList[i] = amount .. '&thinsp;' .. 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('ingredients_default_sep') 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 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 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