Module:Crafting usage

local p = {}

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

local text = require( i18n.moduleText ) 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 special characters in ingredient names, and returns the correct	pattern depending on the match type -- local function createIngredientPatterns( ingredients, matchTypes ) local patternChars = { ['^'] = '%^';		['$'] = '%$';		['('] = '%(';		[')'] = '%)';		['%'] = '%%';		['.'] = '%.';		['['] = '%[';		[']'] = '%]';		['*'] = '%*';		['+'] = '%+';		['-'] = '%-';		['?'] = '%?';		['\0'] = '%z'; }	local patterns = {} for i, ingredient in ipairs( ingredients ) do		local escaped = ingredient:gsub( '([^%w])', patternChars ) 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 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 text.gsplit( predefinedArgGroups, '%s*;%s*' ) do			local groupData = { args = {} } for arg in 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 text.split( args[1], '%s*,%s*' ) or { mw.title.getCurrentTitle.text } local matchTypes = args.match and args.match:find( ',' ) and 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 = 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 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