Jump to: navigation, search

Module:Crafting usage: Difference between revisions

(Created page with "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*...")
 
mNo edit summary
 
(67 intermediate revisions by 2 users not shown)
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
 
--[[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


    local data = ''
--[[Works out what data is relevant to the requested ingredients
    if args.category then
        data = f:callParserFunction( '#dpl', {
If the template contains any of the ingredients, returns it with any
            category = args.category,
necessary modifications, and with the crafting arguments parsed
            nottitleregexp = args.ignore,
--]]
            include = '{Crafting}:' .. table.concat( argList, ':' ),
function p.processTemplate( tArgs, ingredientPatterns )
            mode = 'userformat',
local cArgs = {}
            secseparators = '====',
for i, v in pairs( crafting.cArgVals ) do
            multisecseparators = '===='
cArgs[i] = tArgs[i] or tArgs[v]
        } )
end
    else
cArgs.Output = tArgs.Output
        -- #dpl has a limit of four, so do it in chunks of 4
local parsedCArgs = parseCraftingArgs( cArgs )
        for i = 1, #ingredients, 4 do
local requiredFrameNumData = findRequiredFrameNums( parsedCArgs, ingredientPatterns )
            data = data .. f:callParserFunction( '#dpl', {
if not requiredFrameNumData then
                category = 'Recipe using ' .. table.concat( ingredients, '|Recipe using ', i, math.min( i + 3, #ingredients ) ),
return
                nottitleregexp = args.ignore,
end
                include = '{Crafting}:' .. table.concat( argList, ':' ),
                mode = 'userformat',
local newCArgs
                secseparators = '====',
local modified
                multisecseparators = '====',
if requiredFrameNumData == true then
                [1] = '' --Silence errors
newCArgs = parsedCArgs
            } )
else
        end
local argGroups = generateArgGroups( tArgs.arggroups, parsedCArgs )
    end
newCArgs = {}
   
for _, groupData in pairs( argGroups ) do
    -- Comment this next line out if you're not using aliases
local group = groupData.args
    local aliases = mw.loadData( 'Module:Grid/Aliases' )
local relevantFrameNums = findRelevantFrameNums( requiredFrameNumData, group )
   
if not relevantFrameNums then
    local function matchPattern( ingredient, ingredientNum )
for arg in pairs( group ) do
        local matchType = matchTypes
newCArgs[arg] = parsedCArgs[arg]
        if type( matchType ) == 'table' then
end
            matchType = matchTypes[ingredientNum]
else
        end
modified = true
        local pattern
for arg in pairs( group ) do
        local escaped = ingredient:gsub( '([%(%)])', '%%%1' )
newCArgs[arg] = p.extractRelevantFrames( relevantFrameNums, parsedCArgs[arg] )
        if matchType == 'start' then
end
            pattern = '[;:%]]%s*' .. escaped
end
        elseif matchType == 'end' then
end
            pattern = escaped .. '%s*[,;%[]'
end
        elseif matchType == 'any' then
            pattern = escaped
-- Convert arguments back to shapeless format if they were originally
        else
if tArgs[1] then
            pattern = '[;:%]]%s*' .. escaped .. '%s*[,;%[]'
local i = 1
        end
for argNum = 1, 9 do
       
tArgs[argNum] = nil
        return pattern
local cArg = newCArgs[argNum]
    end
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


    local function compareTables( a, b )
--[[Works out which frame is the first frame, and returns its
        for k, v in pairs( a ) do
name, or alias name, for sorting purposes
            if type( b[k] ) ~= type( v ) then
--]]
                return false
function p.getFirstFrameName( frames, subframe )
            end
local frame = frames[1]
            if type( v ) == 'table' then
if not subframe and frame[1] then
                if not compareTables( v, b[k] ) then
return p.getFirstFrameName( frame[1], true )
                    return false
end
                end
            elseif v ~= b[k] then
local alias = frames.aliasReference and frames.aliasReference[1]
                return false
return alias and alias.frame.name or frame.name
            end
end
        end
        for k, v in pairs( b ) do
            if a[k] == nil then
                return false
            end
        end
        return true
    end


    local out = {}
--[[Performs the DPL query which retrieves the arguments from the crafting
    local showDesciption
templates on the pages within the requested categories
    local templates = {}
--]]
    for template in mw.text.gsplit( data, '====' ) do
function dplQuery( category, ignore )
        -- If ignoreusage is empty
test = mw.title.new(category, "Category")
        if template:find( '^%s*|' ) then
test = test.exists
            local tArgs = {}
if test then
            local i = 0
return mw.getCurrentFrame():callParserFunction( '#dpl:', {
            -- Extract the arguments from the DPL query
category = category,
            for tArg in mw.text.gsplit( template, '\n|' ) do
-- nottitleregexp = ignore,
                i = i + 1
include = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ),
                if tArg ~= '' then
mode = 'userformat',
                    local key = argList[i]
secseparators = '====',
                    tArgs[key] = tArg
multisecseparators = '===='
                end
} )
            end
end
           
return nil
            local craftingArgs = {
end
                tArgs[1] or tArgs.A1 or '', tArgs[2] or tArgs.B1 or '', tArgs[3] or tArgs.C1 or '',
                tArgs[4] or tArgs.A2 or '', tArgs[5] or tArgs.B2 or '', tArgs[6] or tArgs.C2 or '',
                tArgs[7] or tArgs.A3 or '', tArgs[8] or tArgs.B3 or '', tArgs[9] or tArgs.C3 or '',
                Output = tArgs.Output
            }
           
            local expandedFrames = {}
            local hasIngredient
            local argsWithIngredient = {}
            local argGroups = {}
            for i, v in pairs( craftingArgs ) do
                if v ~= '' then
                    if aliases then
                        expandedFrames[i] = {}
                        local expandedFrame = {}
                        for frame in mw.text.gsplit( v, '%s*;%s*' ) do
                            local parts = grid.getParts( frame )
                            local alias = aliases[parts.name]
                            if alias then
                                local expandedAlias = grid.expandAlias( parts, alias ):gsub( '%s*([%[%]:,;])%s*', '%1' )
                                expandedFrames[i][frame] = expandedAlias:gsub( '([%(%)])', '%%%1' )
                                table.insert( expandedFrame, expandedAlias )
                            else
                                table.insert( expandedFrame, frame )
                            end
                        end
                       
                        v = table.concat( expandedFrame, ';' )
                        craftingArgs[i] = v
                    end
                    if i ~= 'Output' then
                        local delimitedFrames = ';' .. v .. ';'
                        for ingredientNum, ingredient in pairs( ingredients ) do
                            if delimitedFrames:find( matchPattern( ingredient, ingredientNum ) ) then
                                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
            end
           
            if hasIngredient then
                if tArgs.description then
                    showDescription = true
                end
               
                if hasIngredient == 'animated' then
                    if tArgs.arggroups then
                        for argGroup in mw.text.gsplit( tArgs.arggroups, '%s*;%s*' ) do
                            local group = {}
                            local _, frameCount
                            for arg in mw.text.gsplit( argGroup, '%s*,%s*' ) do
                                if not tArgs[1] then
                                    arg = shapedToAnon[arg]
                                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
--[[The main body, which retrieves the data, and returns the relevant
                for i, v in ipairs( templates ) do
crafting templates, sorted alphabetically
                    if compareTables( v, tArgs ) then
--]]
                        found = true
function p.dpl( f )
                        break
local args = f
                    end
if f == mw.getCurrentFrame() then
                end
args = f:getParent().args
                if not found then
else
                    table.insert( templates, tArgs )
f = mw.getCurrentFrame()
                end
end
            end
local ingredients = args[1] and mw.text.split( args[1], '%s*,%s*' ) or { mw.title.getCurrentTitle().text }
        end
local matchTypes = args.match and args.match:find( ',' ) and mw.text.split( args.match, '%s*,%s*' ) or args.match
    end
local ingredientPatterns = createIngredientPatterns( ingredients, matchTypes )
    if #templates == 0 then
        return '[[Category:Empty crafting usage]]'
local data
    end
if args.category then
   
data = dplQuery( args.category, args.ignore )
    templates[1].head = '1'
else
    templates[1].showname = '1'
-- DPL has a limit of four categories, so do it in chunks
    if showDescription and args.showdesciption ~= '0' or args.showdesciption == '1' then
data = {}
        templates[1].showdescription = '1'
local dataNum = 1
    end
local ingredientCount = #ingredients
    if not args.continue then
local catParts = mw.text.split( i18n.queryCategory, '%$1' )
        templates[#templates].foot = '1'
for i = 1, ingredientCount, 4 do
    end
data[dataNum] = dplQuery(
   
catParts[1] .. table.concat(
    local crafting = require( 'Module:Crafting' )
ingredients,
    local out = {}
catParts[2] .. '|' .. catParts[1],
    for i, v in ipairs( templates ) do
i,
        table.insert( out, crafting.table( v ) )
math.min( i + 3, ingredientCount )
    end
) .. catParts[2],
   
args.ignore
    return table.concat( out, '\n' )
)
dataNum = dataNum + 1
end
data = table.concat( data )
end
if data == nil or data == "" then
return "Recipe Category not found"
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
end
return p
return p

Latest revision as of 00:32, 15 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 )
	test = mw.title.new(category, "Category")
	test = test.exists
	if test then
		return mw.getCurrentFrame():callParserFunction( '#dpl:', {
			category = category,
			-- nottitleregexp = ignore,
			include = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ),
			mode = 'userformat',
			secseparators = '====',
			multisecseparators = '===='
		} )
	end
	return nil
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
	
	if data == nil or data == "" then
		return "Recipe Category not found"
	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.