Jump to: navigation, search

Module:Crafting usage: Difference between revisions

m (removing tests)
(Attempting overwrite with current version on MCWiki)
Line 1: Line 1:
local p = {}
local p = {}
function p.dpl( f )
local args = f:getParent().args
local grid = require( 'Module:Grid' )
local ingredients = args[1] and mw.text.split( args[1], '%s*,%s*' ) or { mw.title.getCurrentTitle().text }
local matchTypes = args.match and args.match:find( ',' ) and mw.text.split( args.match, '%s*,%s*' ) or args.match


local argList = {
local i18n = {
'ignoreusage', 'upcoming', 'name', 'ingredients', 'arggroups',
emptyCategory = 'Empty crafting usage',
1, 2, 3, 4, 5, 6, 7, 8, 9,
moduleCrafting = [[Module:Crafting]],
'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3',
moduleSlot = [[Module:Inventory slot]],
'Output', 'description', 'fixed', 'notfixed'
queryCategory = 'Recipe using $1',
}
templateCrafting = 'Crafting',
local anonToShaped = { 'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3' }
}
local shapedToAnon = { A1 = 1, B1 = 2, C1 = 3, A2 = 4, B2 = 5, C2 = 5, A3 = 6, B3 = 7, C3 = 8 }
p.i18n = i18n
 
local slot = require( i18n.moduleSlot )
local crafting = require( i18n.moduleCrafting )
local argList = {
'ignoreusage', 'upcoming', 'name', 'ingredients', 'arggroups',
1, 2, 3, 4, 5, 6, 7, 8, 9,
'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3',
'Output', 'description', 'fixed', 'notfixed',
'A1title', 'A1link', 'B1title', 'B1link', 'C1title', 'C1link',
'A2title', 'A2link', 'B2title', 'B2link', 'C2title', 'C2link',
'A3title', 'A3link', 'B3title', 'B3link', 'C3title', 'C3link',
'Otitle', 'Olink',
}
local prefixes = slot.i18n.prefixes


local data = ''
--[[Escapes the parenthesis in ingredient names, and returns the correct
if args.category then
pattern depending on the match type
data = f:callParserFunction( '#dpl', {
--]]
category = args.category,
local function createIngredientPatterns( ingredients, matchTypes )
nottitleregexp = args.ignore,
local patterns = {}
include = '{Crafting}:' .. table.concat( argList, ':' ),
for i, ingredient in ipairs( ingredients ) do
mode = 'userformat',
local escaped = ingredient:gsub( '([()])', '%%%1' )
secseparators = '====',
if not matchTypes then
multisecseparators = '===='
patterns[i] = '^' .. escaped .. '$'
} )
else
else
local matchType = matchTypes[i] or matchTypes
-- #dpl has a limit of four, so do it in chunks of 4
if matchType == 'start' then
for i = 1, #ingredients, 4 do
patterns[i] = '^' .. escaped
data = data .. f:callParserFunction( '#dpl', {
elseif matchType == 'end' then
category = 'Recipe using ' .. table.concat( ingredients, '|Recipe using ', i, math.min( i + 3, #ingredients ) ),
patterns[i] = escaped .. '$'
nottitleregexp = args.ignore,
else
include = '{Crafting}:' .. table.concat( argList, ':' ),
patterns[i] = escaped
mode = 'userformat',
end
secseparators = '====',
multisecseparators = '===='
} )
end
end
end
end
-- Comment this next line out if you're not using aliases
return patterns
local aliases = mw.loadData( 'Module:Grid/Aliases' )
end
 
local function matchPattern( ingredient, ingredientNum )
--[[Extracts the anonymous pipe-delimited arguments from the
local matchType = matchTypes
DPL query into a table with the corresponding keys, skipping
if type( matchType ) == 'table' then
templates with `ignoreusage` set, and skipping duplicate templates
matchType = matchTypes[ingredientNum]
--]]
 
local extractArgs
do
local seen = {}
extractArgs = function( template )
-- Check for duplicate template or `ignoreusage` arg
if seen[template] or not template:find( '^\n|' ) then
return
end
end
local pattern
local escaped = ingredient:gsub( '([%(%)])', '%%%1' )
seen[template] = true
if matchType == 'start' then
pattern = '[;:%]]%s*' .. escaped
local tArgs = {}
elseif matchType == 'end' then
local i = 1
pattern = escaped .. '%s*[,;%[]'
for arg in mw.text.gsplit( template, '\n|' ) do
elseif matchType == 'any' then
if arg ~= '' then
pattern = escaped
tArgs[argList[i]] = arg
else
end
pattern = '[;:%]]%s*' .. escaped .. '%s*[,;%[]'
i = i + 1
end
end
return pattern
tArgs.nocat = '1'
return tArgs
end
end
end


local function compareTables( a, b )
--[[Loops through the crafting args and parses them, with alias reference data
for k, v in pairs( a ) do
if type( b[k] ) ~= type( v ) then
Identical slots reuse the same table, to allow them to be compared like strings
return false
--]]
local function parseCraftingArgs( cArgs )
local parsedFrameText = {}
local parsedCArgs = {}
for arg, frameText in pairs( cArgs ) do
if frameText then
local randomise = arg == 'Output' and 'never' or nil
local frames = not randomise and parsedFrameText[frameText]
if not frames then
frames = slot.parseFrameText( frameText, randomise, true )
parsedFrameText[frameText] = frames
end
end
if type( v ) == 'table' then
parsedCArgs[arg] = frames
if not compareTables( v, b[k] ) then
end
return false
end
return parsedCArgs
end
 
-- Loops through the wanted ingredients, and checks if the name contains it
local function containsIngredients( name, ingredientPatterns )
for _, ingredient in pairs( ingredientPatterns ) do
if name:find( ingredient ) then
return true
end
end
return false
end
 
--[[Loops through the crafting ingredients and find which parameters and
frames contain the wanted ingredients
Returns a table if any matches are found, the table contains tables of
required frame numbers, or true if all of them are required
--]]
local function findRequiredFrameNums( parsedCArgs, ingredientPatterns )
local requiredFrameNums = {}
local hasRequiredFrames
for arg, frames in pairs( parsedCArgs ) do
if arg ~= 'Output' then
local requiredFrames = {}
local count = 0
for i, frame in ipairs( frames ) do
if containsIngredients( frame.name or frame[1].name, ingredientPatterns ) then
requiredFrames[i] = true
count = count + 1
end
end
elseif v ~= b[k] then
end
return false
if count > 0 then
if count == #frames then
return true
end
hasRequiredFrames = true
requiredFrames.count = count
requiredFrameNums[arg] = requiredFrames
end
end
end
end
for k, v in pairs( b ) do
end
if a[k] == nil then
return false
return hasRequiredFrames and requiredFrameNums
end
 
--[[Generates the argument groups, either using the template's specified
groups, or automatically based on the number of frames in each slot
--]]
local function generateArgGroups( predefinedArgGroups, parsedCArgs )
local argGroups = {}
if predefinedArgGroups or '' ~= '' then
local i = 1
for argGroup in mw.text.gsplit( predefinedArgGroups, '%s*;%s*' ) do
local groupData = { args = {} }
for arg in mw.text.gsplit( argGroup, '%s*,%s*' ) do
arg = tonumber( arg ) or arg
if not groupData.count then
groupData.count = #parsedCArgs[arg]
end
groupData.args[arg] = true
end
argGroups[i] = groupData
i = i + 1
end
else
for arg, frames in pairs( parsedCArgs ) do
local framesLen = #frames
if framesLen > 0 then
local groupName = framesLen
local alias = frames.aliasReference and frames.aliasReference[1]
if alias and alias.length == framesLen and
alias.frame.name:find( '^' .. prefixes.any .. ' ' )
then
groupName = alias.frame.name
end
local groupData = argGroups[groupName]
if not groupData then
groupData = {
args = {},
count = framesLen
}
argGroups[groupName] = groupData
end
groupData.args[arg] = true
end
end
end
end
return true
end
end
return argGroups
end


local out = {}
--[[Adds together the required frames from each slot in this group
local showDesciption
to get the total amount of frames which are relevant
local templates = {}
for template in mw.text.gsplit( data, '====' ) do
Returns a table with the relevant frame numbers, if any are relevant
-- If ignoreusage is empty
--]]
if template:find( '^%s*|' ) then
local function findRelevantFrameNums( requiredFrameNumData, group )
local tArgs = {}
local relevantFrameNums = {}
local i = 0
local hasRelevantFrames
-- Extract the arguments from the DPL query
for arg in pairs( group ) do
for tArg in mw.text.gsplit( template, '\n|' ) do
local requiredFrameNums = requiredFrameNumData[arg]
i = i + 1
if requiredFrameNums and arg ~= 'Output' then
if tArg ~= '' then
for frameNum in pairs( requiredFrameNums ) do
local key = argList[i]
-- Have to use pairs as it contains a non-sequential set of numbers
tArgs[key] = tArg
-- so we have to skip over the extra data in the table
if frameNum ~= 'count' then
hasRelevantFrames = true
relevantFrameNums[frameNum] = true
end
end
end
end
local craftingArgs = {
relevantFrameNums.count = math.max(
tArgs[1] or tArgs.A1 or '', tArgs[2] or tArgs.B1 or '', tArgs[3] or tArgs.C1 or '',
requiredFrameNums.count or 0,
tArgs[4] or tArgs.A2 or '', tArgs[5] or tArgs.B2 or '', tArgs[6] or tArgs.C2 or '',
relevantFrameNums.count or 0
tArgs[7] or tArgs.A3 or '', tArgs[8] or tArgs.B3 or '', tArgs[9] or tArgs.C3 or '',
)
Output = tArgs.Output
end
}
end
local expandedFrames = {}
return hasRelevantFrames and relevantFrameNums
local hasIngredient
end
local argsWithIngredient = {}
 
local argGroups = {}
--[[Loops through the relevant frame numbers and extracts them
for i, v in pairs( craftingArgs ) do
into a new table, taking care of moving any alias references
if v ~= '' then
and cleaning up any unnecessary subframes
if aliases then
--]]
expandedFrames[i] = {}
function p.extractRelevantFrames( relevantFrameNums, frames )
local expandedFrame = {}
local relevantFrames = { randomise = frames.randomise }
for frame in mw.text.gsplit( v, '%s*;%s*' ) do
local newFrameNum = 1
local parts = grid.getParts( frame )
for frameNum, frame in ipairs( frames ) do
local alias = aliases[parts.name]
local relevantFrame = relevantFrameNums == true or relevantFrameNums[frameNum]
if alias then
if relevantFrame then
local expandedAlias = grid.expandAlias( parts, alias ):gsub( '%s*([%[%]:,;])%s*', '%1' )
if not frame[1] then
expandedFrames[i][frame] = expandedAlias:gsub( '([%(%)])', '%%%1' )
local alias = frames.aliasReference and frames.aliasReference[frameNum]
table.insert( expandedFrame, expandedAlias )
local moveAlias = true
else
if alias and relevantFrameNums ~= true then
table.insert( expandedFrame, frame )
for i = frameNum, alias.length do
end
if not relevantFrameNums[i] then
moveAlias = false
break
end
end
v = table.concat( expandedFrame, ';' )
craftingArgs[i] = v
end
end
if i ~= 'Output' then
end
local delimitedFrames = ';' .. v .. ';'
if alias and moveAlias then
for ingredientNum, ingredient in pairs( ingredients ) do
if not relevantFrames.aliasReference then
if delimitedFrames:find( matchPattern( ingredient, ingredientNum ) ) then
relevantFrames.aliasReference = {}
if not v:find( ';' ) then
hasIngredient = 'static'
elseif not hasIngredient then
hasIngredient = 'animated'
end
argsWithIngredient[i] = true
end
end
end
if not tArgs.arggroups and hasIngredient ~= 'static' then
local _, frameCount = v:gsub( ';', '' )
if frameCount > 0 then
frameCount = frameCount + 1
local group = argGroups[frameCount]
if not group then
group = { args = {} }
argGroups[frameCount] = group
end
group.count = frameCount
group.args[i] = true
end
end
end
relevantFrames.aliasReference[newFrameNum] = alias
end
end
end
end
if hasIngredient then
relevantFrames[newFrameNum] = frame
if tArgs.description then
newFrameNum = newFrameNum + 1
showDescription = true
end
end
end
if hasIngredient == 'animated' then
-- Move frames in subframe to main frames, if the subframe
if tArgs.arggroups then
-- is the only frame
for argGroup in mw.text.gsplit( tArgs.arggroups, '%s*;%s*' ) do
if not relevantFrames[2] and relevantFrames[1][1] then
local group = {}
relevantFrames = relevantFrames[1]
local _, frameCount
end
for arg in mw.text.gsplit( argGroup, '%s*,%s*' ) do
if not tArgs[1] then
return relevantFrames
arg = shapedToAnon[arg]
end
end
if not frameCount then
_, frameCount = craftingArgs[arg]:gsub( ';', '' )
end
group[arg] = true
end
table.insert( argGroups, { count = frameCount + 1, args = group } )
end
end
for _, groupData in pairs( argGroups ) do
local frameCount = groupData.count
local group = groupData.args
local requiredFrames = {}
local requiredFramesCount = 0
for arg in pairs( group ) do
if argsWithIngredient[arg] then
local frames = craftingArgs[arg]
local frameNum = 0
for frame in mw.text.gsplit( frames, '%s*;%s*' ) do
frameNum = frameNum + 1
if not requiredFrames[frameNum] then
local delimitedFrame = ';' .. frame .. ';'
for ingredientNum, ingredient in pairs( ingredients ) do
if delimitedFrame:find( matchPattern( ingredient, ingredientNum ) ) then
requiredFrames[frameNum] = true
requiredFramesCount = requiredFramesCount + 1
end
end
end
end
end
end
-- Not all frames will be used
if requiredFramesCount > 0 and requiredFramesCount < frameCount then
for arg in pairs( group ) do
local frames = craftingArgs[arg]
local newFrames = {}
local frameNum = 0
for frame in mw.text.gsplit( frames, '%s*;%s*' ) do
frameNum = frameNum + 1
if requiredFrames[frameNum] then
table.insert( newFrames, frame )
end
end
newFrames = table.concat( newFrames, ';' )
-- If the whole expanded alias survived, collapse it again
if expandedFrames[arg] then
for frame, expandedAlias in pairs( expandedFrames[arg] ) do
--newFrames = 'blah' .. expandedAlias
newFrames = newFrames:gsub( expandedAlias, frame )
end
end
local tArg = arg
if arg ~= 'Output' and not tArgs[1] then
tArg = anonToShaped[arg]
end
tArgs[tArg] = newFrames
end
-- Let Module:Crafting handle the name and ingredients columns
tArgs.name = nil
tArgs.ingredients = nil
end
end
end
tArgs.nocat = '1'


local found = false
--[[Works out what data is relevant to the requested ingredients
for i, v in ipairs( templates ) do
if compareTables( v, tArgs ) then
If the template contains any of the ingredients, returns it with any
found = true
necessary modifications, and with the crafting arguments parsed
break
--]]
end
function p.processTemplate( tArgs, ingredientPatterns )
local cArgs = {}
for i, v in pairs( crafting.cArgVals ) do
cArgs[i] = tArgs[i] or tArgs[v]
end
cArgs.Output = tArgs.Output
local parsedCArgs = parseCraftingArgs( cArgs )
local requiredFrameNumData = findRequiredFrameNums( parsedCArgs, ingredientPatterns )
if not requiredFrameNumData then
return
end
local newCArgs
local modified
if requiredFrameNumData == true then
newCArgs = parsedCArgs
else
local argGroups = generateArgGroups( tArgs.arggroups, parsedCArgs )
newCArgs = {}
for _, groupData in pairs( argGroups ) do
local group = groupData.args
local relevantFrameNums = findRelevantFrameNums( requiredFrameNumData, group )
if not relevantFrameNums then
for arg in pairs( group ) do
newCArgs[arg] = parsedCArgs[arg]
end
end
if not found then
else
table.insert( templates, tArgs )
modified = true
for arg in pairs( group ) do
newCArgs[arg] = p.extractRelevantFrames( relevantFrameNums, parsedCArgs[arg] )
end
end
end
end
end
end
end
end
if #templates == 0 then
return '[[Category:Empty crafting usage]]'
-- Convert arguments back to shapeless format if they were originally
if tArgs[1] then
local i = 1
for argNum = 1, 9 do
tArgs[argNum] = nil
local cArg = newCArgs[argNum]
if cArg then
tArgs[i] = cArg
i = i + 1
end
end
else
for i, arg in pairs( crafting.cArgVals ) do
tArgs[arg] = newCArgs[i]
end
end
tArgs.Output = newCArgs.Output
tArgs.parsed = true
-- Let Module:Recipe table generate these
-- with the modified crafting args
if modified then
tArgs.name = nil
tArgs.ingredients = nil
end
return tArgs
end
 
--[[Works out which frame is the first frame, and returns its
name, or alias name, for sorting purposes
--]]
function p.getFirstFrameName( frames, subframe )
local frame = frames[1]
if not subframe and frame[1] then
return p.getFirstFrameName( frame[1], true )
end
local alias = frames.aliasReference and frames.aliasReference[1]
return alias and alias.frame.name or frame.name
end
 
--[[Performs the DPL query which retrieves the arguments from the crafting
templates on the pages within the requested categories
--]]
function dplQuery( category, ignore )
return mw.getCurrentFrame():callParserFunction( '#dpl:', {
category = category,
nottitleregexp = ignore,
include = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ),
mode = 'userformat',
secseparators = '====',
multisecseparators = '===='
} )
end
 
--[[The main body, which retrieves the data, and returns the relevant
crafting templates, sorted alphabetically
--]]
function p.dpl( f )
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
else
f = mw.getCurrentFrame()
end
local ingredients = args[1] and mw.text.split( args[1], '%s*,%s*' ) or { mw.title.getCurrentTitle().text }
local matchTypes = args.match and args.match:find( ',' ) and mw.text.split( args.match, '%s*,%s*' ) or args.match
local ingredientPatterns = createIngredientPatterns( ingredients, matchTypes )
local data
if args.category then
data = dplQuery( args.category, args.ignore )
else
-- DPL has a limit of four categories, so do it in chunks
data = {}
local dataNum = 1
local ingredientCount = #ingredients
local catParts = mw.text.split( i18n.queryCategory, '%$1' )
for i = 1, ingredientCount, 4 do
data[dataNum] = dplQuery(
catParts[1] .. table.concat(
ingredients,
catParts[2] .. '|' .. catParts[1],
i,
math.min( i + 3, ingredientCount )
) .. catParts[2],
args.ignore
)
dataNum = dataNum + 1
end
data = table.concat( data )
end
local showDescription
local templates = {}
local i = 1
for templateArgs in mw.text.gsplit( data:sub( 5 ), '====' ) do
local tArgs = extractArgs( templateArgs )
local newArgs = tArgs and p.processTemplate( tArgs, ingredientPatterns )
if newArgs then
if tArgs.description then
showDescription = '1'
end
templates[i] = {
args = newArgs,
sortKey = mw.ustring.lower(
( newArgs.name or p.getFirstFrameName( newArgs.Output ) )
:gsub( '^' .. prefixes.any .. ' ', '' )
:gsub( '^' .. prefixes.matching .. ' ', '' )
:gsub( '^%[%[', '' )
:gsub( '^[^|]+|', '' )
),
}
i = i + 1
end
end
end
templates[1].head = '1'
local templateCount = #templates
templates[1].showname = '1'
if templateCount == 0 then
if showDescription and args.showdesciption ~= '0' or args.showdesciption == '1' then
return f:expandTemplate{ title = 'Translation category', args = { i18n.emptyCategory, project = '0' } }
templates[1].showdescription = '1'
end
end
table.sort( templates, function( a, b )
return a.sortKey < b.sortKey
end )
local initialArgs = templates[1].args
initialArgs.head = '1'
initialArgs.showname = '1'
initialArgs.showdescription = showDescription
if not args.continue then
if not args.continue then
templates[#templates].foot = '1'
templates[templateCount].args.foot = '1'
end
end
local crafting = require( 'Module:Crafting' )
local out = {}
local out = {}
for i, v in ipairs( templates ) do
for i, template in ipairs( templates ) do
table.insert( out, crafting.table( v ) )
out[i] = crafting.table( template.args )
end
end
return table.concat( out, '\n' )
return table.concat( out, '\n' )
end
end
return p
return p

Revision as of 12:03, 11 May 2017

Documentation

This module implements {{Crafting usage}}.

Dependencies

The above documentation is transcluded from Module:Crafting usage/doc.

This module implements {{Crafting usage}}.

Dependencies


local p = {}

local i18n = {
	emptyCategory = 'Empty crafting usage',
	moduleCrafting = [[Module:Crafting]],
	moduleSlot = [[Module:Inventory slot]],
	queryCategory = 'Recipe using $1',
	templateCrafting = 'Crafting',
}
p.i18n = i18n

local slot = require( i18n.moduleSlot )
local crafting = require( i18n.moduleCrafting )
local argList = {
	'ignoreusage', 'upcoming', 'name', 'ingredients', 'arggroups',
	1, 2, 3, 4, 5, 6, 7, 8, 9,
	'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3',
	'Output', 'description', 'fixed', 'notfixed',
	'A1title', 'A1link', 'B1title', 'B1link', 'C1title', 'C1link',
	'A2title', 'A2link', 'B2title', 'B2link', 'C2title', 'C2link',
	'A3title', 'A3link', 'B3title', 'B3link', 'C3title', 'C3link',
	'Otitle', 'Olink',
}
local prefixes = slot.i18n.prefixes

--[[Escapes the parenthesis in ingredient names, and returns the correct
	pattern depending on the match type
--]]
local function createIngredientPatterns( ingredients, matchTypes )
	local patterns = {}
	for i, ingredient in ipairs( ingredients ) do
		local escaped = ingredient:gsub( '([()])', '%%%1' )
		if not matchTypes then
			patterns[i] = '^' .. escaped .. '$'
		else
			local matchType = matchTypes[i] or matchTypes
			if matchType == 'start' then
				patterns[i] = '^' .. escaped
			elseif matchType == 'end' then
				patterns[i] = escaped .. '$'
			else
				patterns[i] = escaped
			end
		end
	end
	
	return patterns
end

--[[Extracts the anonymous pipe-delimited arguments from the
	DPL query into a table with the corresponding keys, skipping
	templates with `ignoreusage` set, and skipping duplicate templates
--]]

local extractArgs
do
	local seen = {}
	extractArgs = function( template )
		-- Check for duplicate template or `ignoreusage` arg
		if seen[template] or not template:find( '^\n|' ) then
			return
		end
		
		seen[template] = true
		
		local tArgs = {}
		local i = 1
		for arg in mw.text.gsplit( template, '\n|' ) do
			if arg ~= '' then
				tArgs[argList[i]] = arg
			end
			i = i + 1
		end
		
		tArgs.nocat = '1'
		
		return tArgs
	end
end

--[[Loops through the crafting args and parses them, with alias reference data
	
	Identical slots reuse the same table, to allow them to be compared like strings
--]]
local function parseCraftingArgs( cArgs )
	local parsedFrameText = {}
	local parsedCArgs = {}
	for arg, frameText in pairs( cArgs ) do
		if frameText then
			local randomise = arg == 'Output' and 'never' or nil
			local frames = not randomise and parsedFrameText[frameText]
			if not frames then
				frames = slot.parseFrameText( frameText, randomise, true )
				parsedFrameText[frameText] = frames
			end
			parsedCArgs[arg] = frames
		end
	end
	
	return parsedCArgs
end

-- Loops through the wanted ingredients, and checks if the name contains it
local function containsIngredients( name, ingredientPatterns )
	for _, ingredient in pairs( ingredientPatterns ) do
		if name:find( ingredient ) then
			return true
		end
	end
	
	return false
end

--[[Loops through the crafting ingredients and find which parameters and
	frames contain the wanted ingredients
	
	Returns a table if any matches are found, the table contains tables of
	required frame numbers, or true if all of them are required
--]]
local function findRequiredFrameNums( parsedCArgs, ingredientPatterns )
	local requiredFrameNums = {}
	local hasRequiredFrames
	for arg, frames in pairs( parsedCArgs ) do
		if arg ~= 'Output' then
			local requiredFrames = {}
			local count = 0
			for i, frame in ipairs( frames ) do
				if containsIngredients( frame.name or frame[1].name, ingredientPatterns ) then
					requiredFrames[i] = true
					count = count + 1
				end
			end
			if count > 0 then
				if count == #frames then
					return true
				end
				
				hasRequiredFrames = true
				requiredFrames.count = count
				requiredFrameNums[arg] = requiredFrames
			end
		end
	end
	
	return hasRequiredFrames and requiredFrameNums
end

--[[Generates the argument groups, either using the template's specified
	groups, or automatically based on the number of frames in each slot
--]]
local function generateArgGroups( predefinedArgGroups, parsedCArgs )
	local argGroups = {}
	if predefinedArgGroups or '' ~= '' then
		local i = 1
		for argGroup in mw.text.gsplit( predefinedArgGroups, '%s*;%s*' ) do
			local groupData = { args = {} }
			for arg in mw.text.gsplit( argGroup, '%s*,%s*' ) do
				arg = tonumber( arg ) or arg
				if not groupData.count then
					groupData.count = #parsedCArgs[arg]
				end
				groupData.args[arg] = true
			end
			argGroups[i] = groupData
			i = i + 1
		end
	else
		for arg, frames in pairs( parsedCArgs ) do
			local framesLen = #frames
			if framesLen > 0 then
				local groupName = framesLen
				local alias = frames.aliasReference and frames.aliasReference[1]
				if alias and alias.length == framesLen and
					alias.frame.name:find( '^' .. prefixes.any .. ' ' )
				then
					groupName = alias.frame.name
				end
				
				local groupData = argGroups[groupName]
				if not groupData then
					groupData = {
						args = {},
						count = framesLen
					}
					argGroups[groupName] = groupData
				end
				groupData.args[arg] = true
			end
		end
	end
	
	return argGroups
end

--[[Adds together the required frames from each slot in this group
	to get the total amount of frames which are relevant
	
	Returns a table with the relevant frame numbers, if any are relevant
--]]
local function findRelevantFrameNums( requiredFrameNumData, group )
	local relevantFrameNums = {}
	local hasRelevantFrames
	for arg in pairs( group ) do
		local requiredFrameNums = requiredFrameNumData[arg]
		if requiredFrameNums and arg ~= 'Output' then
			for frameNum in pairs( requiredFrameNums ) do
				-- Have to use pairs as it contains a non-sequential set of numbers
				-- so we have to skip over the extra data in the table
				if frameNum ~= 'count' then
					hasRelevantFrames = true
					relevantFrameNums[frameNum] = true
				end
			end
			
			relevantFrameNums.count = math.max(
				requiredFrameNums.count or 0,
				relevantFrameNums.count or 0
			)
		end
	end
	
	return hasRelevantFrames and relevantFrameNums
end

--[[Loops through the relevant frame numbers and extracts them
	into a new table, taking care of moving any alias references
	and cleaning up any unnecessary subframes
--]]
function p.extractRelevantFrames( relevantFrameNums, frames )
	local relevantFrames = { randomise = frames.randomise }
	local newFrameNum = 1
	for frameNum, frame in ipairs( frames ) do
		local relevantFrame = relevantFrameNums == true or relevantFrameNums[frameNum]
		if relevantFrame then
			if not frame[1] then
				local alias = frames.aliasReference and frames.aliasReference[frameNum]
				local moveAlias = true
				if alias and relevantFrameNums ~= true then
					for i = frameNum, alias.length do
						if not relevantFrameNums[i] then
							moveAlias = false
							break
						end
					end
				end
				if alias and moveAlias then
					if not relevantFrames.aliasReference then
						relevantFrames.aliasReference = {}
					end
					relevantFrames.aliasReference[newFrameNum] = alias
				end
			end
			
			relevantFrames[newFrameNum] = frame
			newFrameNum = newFrameNum + 1
		end
	end
	
	-- Move frames in subframe to main frames, if the subframe
	-- is the only frame
	if not relevantFrames[2] and relevantFrames[1][1] then
		relevantFrames = relevantFrames[1]
	end
	
	return relevantFrames
end

--[[Works out what data is relevant to the requested ingredients
	
	If the template contains any of the ingredients, returns it with any
	necessary modifications, and with the crafting arguments parsed
--]]
function p.processTemplate( tArgs, ingredientPatterns )
	local cArgs = {}
	for i, v in pairs( crafting.cArgVals ) do
		cArgs[i] = tArgs[i] or tArgs[v]
	end
	cArgs.Output = tArgs.Output
	local parsedCArgs = parseCraftingArgs( cArgs )
	
	local requiredFrameNumData = findRequiredFrameNums( parsedCArgs, ingredientPatterns )
	if not requiredFrameNumData then
		return
	end
	
	local newCArgs
	local modified
	if requiredFrameNumData == true then
		newCArgs = parsedCArgs
	else
		local argGroups = generateArgGroups( tArgs.arggroups, parsedCArgs )
		newCArgs = {}
		for _, groupData in pairs( argGroups ) do
			local group = groupData.args
			local relevantFrameNums = findRelevantFrameNums( requiredFrameNumData, group )
			if not relevantFrameNums then
				for arg in pairs( group ) do
					newCArgs[arg] = parsedCArgs[arg]
				end
			else
				modified = true
				for arg in pairs( group ) do
					newCArgs[arg] = p.extractRelevantFrames( relevantFrameNums, parsedCArgs[arg] )
				end
			end
		end
	end
	
	-- Convert arguments back to shapeless format if they were originally
	if tArgs[1] then
		local i = 1
		for argNum = 1, 9 do
			tArgs[argNum] = nil
			local cArg = newCArgs[argNum]
			if cArg then
				tArgs[i] = cArg
				i = i + 1
			end
		end
	else
		for i, arg in pairs( crafting.cArgVals ) do
			tArgs[arg] = newCArgs[i]
		end
	end
	tArgs.Output = newCArgs.Output
	tArgs.parsed = true
	
	-- Let Module:Recipe table generate these
	-- with the modified crafting args
	if modified then
		tArgs.name = nil
		tArgs.ingredients = nil
	end
	
	return tArgs
end

--[[Works out which frame is the first frame, and returns its
	name, or alias name, for sorting purposes
--]]
function p.getFirstFrameName( frames, subframe )
	local frame = frames[1]
	if not subframe and frame[1] then
		return p.getFirstFrameName( frame[1], true )
	end
	
	local alias = frames.aliasReference and frames.aliasReference[1]
	return alias and alias.frame.name or frame.name
end

--[[Performs the DPL query which retrieves the arguments from the crafting
	templates on the pages within the requested categories
--]]
function dplQuery( category, ignore )
	return mw.getCurrentFrame():callParserFunction( '#dpl:', {
		category = category,
		nottitleregexp = ignore,
		include = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ),
		mode = 'userformat',
		secseparators = '====',
		multisecseparators = '===='
	} )
end

--[[The main body, which retrieves the data, and returns the relevant
	crafting templates, sorted alphabetically
--]]
function p.dpl( f )
	local args = f
	if f == mw.getCurrentFrame() then
		args = f:getParent().args
	else
		f = mw.getCurrentFrame()
	end
	local ingredients = args[1] and mw.text.split( args[1], '%s*,%s*' ) or { mw.title.getCurrentTitle().text }
	local matchTypes = args.match and args.match:find( ',' ) and mw.text.split( args.match, '%s*,%s*' ) or args.match
	local ingredientPatterns = createIngredientPatterns( ingredients, matchTypes )
	
	local data
	if args.category then
		data = dplQuery( args.category, args.ignore )
	else
		-- DPL has a limit of four categories, so do it in chunks
		data = {}
		local dataNum = 1
		local ingredientCount = #ingredients
		local catParts = mw.text.split( i18n.queryCategory, '%$1' )
		for i = 1, ingredientCount, 4 do
			data[dataNum] = dplQuery(
				catParts[1] .. table.concat(
					ingredients,
					catParts[2] .. '|' .. catParts[1],
					i,
					math.min( i + 3, ingredientCount )
				) .. catParts[2],
				args.ignore
			)
			dataNum = dataNum + 1
		end
		data = table.concat( data )
	end
	
	local showDescription
	local templates = {}
	local i = 1
	for templateArgs in mw.text.gsplit( data:sub( 5 ), '====' ) do
		local tArgs = extractArgs( templateArgs )
		local newArgs = tArgs and p.processTemplate( tArgs, ingredientPatterns )
		if newArgs then
			if tArgs.description then
				showDescription = '1'
			end
			
			templates[i] = {
				args = newArgs,
				sortKey = mw.ustring.lower(
					( newArgs.name or p.getFirstFrameName( newArgs.Output ) )
						:gsub( '^' .. prefixes.any .. ' ', '' )
						:gsub( '^' .. prefixes.matching .. ' ', '' )
						:gsub( '^%[%[', '' )
						:gsub( '^[^|]+|', '' )
				),
			}
			i = i + 1
		end
	end
	
	local templateCount = #templates
	if templateCount == 0 then
		return f:expandTemplate{ title = 'Translation category', args = { i18n.emptyCategory, project = '0' } }
	end
	
	table.sort( templates, function( a, b )
		return a.sortKey < b.sortKey
	end )
	
	local initialArgs = templates[1].args
	initialArgs.head = '1'
	initialArgs.showname = '1'
	initialArgs.showdescription = showDescription
	if not args.continue then
		templates[templateCount].args.foot = '1'
	end
	
	local out = {}
	for i, template in ipairs( templates ) do
		out[i] = crafting.table( template.args )
	end
	return table.concat( out, '\n' )
end

return p


Cookies help us deliver our services. By using our services, you agree to our use of cookies.