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', '%PAGE%', } local prefixes = slot.i18n.prefixes

local function map(tbl, func) local newtbl = {} for i,v in pairs(tbl) do       newtbl[i] = func(v) end return newtbl end

-- Flatten a nested array, only doing the numerically-indexed parts. local function flatten(tbl) local newtbl = {} local function _flat(arr) for _, v in ipairs(arr) do			if type(v) == "table" and v[1] then _flat(v) else table.insert(newtbl, v)			end end end _flat(tbl) return newtbl end

--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] = '%z' .. escaped .. '%z' else local matchType = matchTypes[i] or matchTypes if matchType == 'start' then patterns[i] = '%z' .. escaped elseif matchType == 'end' then patterns[i] = escaped .. '%z' 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 -- REQUIREMENT: name starts and ends with the NUL (\0) character. Simplifies operation -- for multiple names in the variable. 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 -- Guess what? If we only take the first we lose the subframes. -- And then 'Cobblestone or Blackstone' starts breaking. local tframe = frame[1] and flatten(frame) or { frame } local names = '\0' .. table.concat(map(tframe, function (fr) return type(fr) == 'table' and fr.name or '' end), '\0') .. '\0'				if containsIngredients( names, 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.	If more than four categories are given, break them down into batches of four. -- local function dplQueryWrapper( category, ignore ) local data = {} if type(category) == 'string' then category = mw.text.split(category, '|') else assert(type(category) == 'table') end local includeStr = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ) local j = 1 local count = #category local catParts = text.split( i18n.queryCategory, '%$1' ) for i = 1, count, 4 do		local catSlice = table.concat(category, '|', i, math.min(i + 3, count)) data[j] = mw.getCurrentFrame:callParserFunction( '#dpl:', {			category = catSlice,			nottitleregexp = ignore,			include = includeStr,			mode = 'userformat',			secseparators = '====',			multisecseparators = '===='		}) j = j + 1 end return table.concat(data) 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 = dplQueryWrapper( args.category, args.ignore ) else -- Need to format the catparts local catParts = text.split( i18n.queryCategory, '%$1' ) local cats = map(ingredients, function (s)			return catParts[1] .. s .. catParts[2]		end) data = dplQueryWrapper( cats, args.ignore ) 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 if f:getTitle.isContentPage then return f:expandTemplate{ title = 'Translation category', args = { i18n.emptyCategory, project = '0' } } end return '' 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] = ( '\n'		          .. crafting.table( template.args ) ) end return table.concat( out, '\n' ) end

return p