(Guess this wasn't a number?) |
m (Revert) |
||
(22 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} |
local p = {} |
||
− | function p.dpl( f ) |
||
− | local args = f:getParent().args |
||
− | local slot = require( 'Module:Inventory slot' ) |
||
− | 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 i18n = { |
|
+ | emptyCategory = 'Empty crafting usage', |
||
− | 'ignoreusage', 'upcoming', 'name', 'ingredients', 'arggroups', |
||
+ | moduleCrafting = [[Module:Crafting]], |
||
− | 1, 2, 3, 4, 5, 6, 7, 8, 9, |
||
+ | moduleSlot = [[Module:Inventory slot]], |
||
− | 'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3', |
||
+ | moduleText = [[Module:Text]], |
||
− | 'Output', 'description', 'fixed', 'notfixed', |
||
+ | queryCategory = 'Recipe using $1', |
||
− | 'A1title', 'A1link', 'B1title', 'B1link', 'C1title', 'C1link', |
||
+ | templateCrafting = 'Crafting', |
||
− | 'A2title', 'A2link', 'B2title', 'B2link', 'C2title', 'C2link', |
||
+ | } |
||
− | 'A3title', 'A3link', 'B3title', 'B3link', 'C3title', 'C3link', |
||
+ | p.i18n = i18n |
||
− | 'Otitle', 'Olink', |
||
− | } |
||
− | local anonToShaped = { 'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3' } |
||
− | local shapedToAnon = { A1 = 1, B1 = 2, C1 = 3, A2 = 4, B2 = 5, C2 = 6, A3 = 7, B3 = 8, C3 = 9 } |
||
− | + | local text = require( i18n.moduleText ) |
|
+ | local slot = require( i18n.moduleSlot ) |
||
− | if args.category then |
||
+ | local crafting = require( i18n.moduleCrafting ) |
||
− | data = f:callParserFunction( '#dpl:', { |
||
+ | local argList = { |
||
− | category = args.category, |
||
+ | 'ignoreusage', 'upcoming', 'name', 'ingredients', 'arggroups', |
||
− | nottitleregexp = args.ignore, |
||
+ | 1, 2, 3, 4, 5, 6, 7, 8, 9, |
||
− | include = '{Crafting}:' .. table.concat( argList, ':' ), |
||
+ | 'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3', |
||
− | mode = 'userformat', |
||
+ | 'Output', 'description', 'fixed', 'notfixed', |
||
− | secseparators = '====', |
||
+ | 'A1title', 'A1link', 'B1title', 'B1link', 'C1title', 'C1link', |
||
− | multisecseparators = '====', |
||
+ | 'A2title', 'A2link', 'B2title', 'B2link', 'C2title', 'C2link', |
||
− | } ) |
||
+ | 'A3title', 'A3link', 'B3title', 'B3link', 'C3title', 'C3link', |
||
− | else |
||
+ | 'Otitle', 'Olink', |
||
− | -- #dpl has a limit of four, so do it in chunks of 4 |
||
+ | '%PAGE%', |
||
− | for i = 1, #ingredients, 4 do |
||
+ | } |
||
− | data = data .. f:callParserFunction( '#dpl:', { |
||
+ | local prefixes = slot.i18n.prefixes |
||
− | category = 'Recipe using ' .. table.concat( ingredients, '|Recipe using ', i, math.min( i + 3, #ingredients ) ), |
||
+ | |||
− | nottitleregexp = args.ignore, |
||
+ | local function map(tbl, func) |
||
− | include = '{Crafting}:' .. table.concat( argList, ':' ), |
||
+ | local newtbl = {} |
||
− | mode = 'userformat', |
||
+ | for i,v in pairs(tbl) do |
||
− | secseparators = '====', |
||
+ | newtbl[i] = func(v) |
||
− | multisecseparators = '====', |
||
+ | 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 |
||
end |
end |
||
+ | return patterns |
||
− | -- Comment this next line out if you're not using aliases |
||
+ | end |
||
− | local aliases = mw.loadData( 'Module:Inventory slot/Aliases' ) |
||
+ | |||
− | |||
+ | --[[Extracts the anonymous pipe-delimited arguments from the |
||
− | local function matchPattern( ingredient, ingredientNum ) |
||
+ | DPL query into a table with the corresponding keys, skipping |
||
− | local matchType = matchTypes |
||
+ | templates with `ignoreusage` set, and skipping duplicate templates |
||
− | if type( matchType ) == 'table' then |
||
+ | --]] |
||
− | 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 |
||
+ | seen[template] = true |
||
− | local escaped = ingredient:gsub( '([%(%)])', '%%%1' ) |
||
+ | |||
− | if matchType == 'start' then |
||
+ | local tArgs = {} |
||
− | pattern = '[;:%]]%s*' .. escaped |
||
+ | local i = 1 |
||
− | elseif matchType == 'end' then |
||
+ | for arg in text.gsplit( template, '\n|' ) do |
||
− | pattern = escaped .. '%s*[,;%[]' |
||
− | + | if arg ~= '' then |
|
+ | tArgs[argList[i]] = arg |
||
− | pattern = escaped |
||
− | + | end |
|
+ | i = i + 1 |
||
− | pattern = '[;:%]]%s*' .. escaped .. '%s*[,;%[]' |
||
end |
end |
||
+ | tArgs.nocat = '1' |
||
− | return pattern |
||
+ | |||
+ | return tArgs |
||
end |
end |
||
+ | end |
||
+ | --[[Loops through the crafting args and parses them, with alias reference data |
||
− | local function compareTables( a, b ) |
||
+ | |||
− | for k, v in pairs( a ) do |
||
+ | Identical slots reuse the same table, to allow them to be compared like strings |
||
− | if type( b[k] ) ~= type( v ) then |
||
+ | --]] |
||
− | 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 |
||
+ | parsedCArgs[arg] = frames |
||
− | if type( v ) == 'table' then |
||
+ | end |
||
− | if not compareTables( v, b[k] ) then |
||
+ | end |
||
− | return false |
||
+ | |||
+ | 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 |
||
+ | end |
||
− | elseif v ~= b[k] then |
||
+ | if count > 0 then |
||
− | return false |
||
+ | if count == #frames then |
||
+ | return true |
||
+ | end |
||
+ | |||
+ | hasRequiredFrames = true |
||
+ | requiredFrames.count = count |
||
+ | requiredFrameNums[arg] = requiredFrames |
||
end |
end |
||
end |
end |
||
+ | end |
||
− | for k, v in pairs( b ) do |
||
+ | |||
− | if a[k] == nil then |
||
+ | return hasRequiredFrames and requiredFrameNums |
||
− | return false |
||
+ | 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 |
end |
||
− | return true |
||
end |
end |
||
+ | |||
+ | return argGroups |
||
+ | end |
||
+ | --[[Adds together the required frames from each slot in this group |
||
− | local out = {} |
||
+ | to get the total amount of frames which are relevant |
||
− | local showDesciption |
||
+ | |||
− | local templates = {} |
||
+ | Returns a table with the relevant frame numbers, if any are relevant |
||
− | for template in mw.text.gsplit( data, '====' ) do |
||
+ | --]] |
||
− | -- If ignoreusage is empty |
||
+ | local function findRelevantFrameNums( requiredFrameNumData, group ) |
||
− | if template:find( '^%s*|' ) then |
||
− | + | local relevantFrameNums = {} |
|
− | + | local hasRelevantFrames |
|
+ | for arg in pairs( group ) do |
||
− | -- Extract the arguments from the DPL query |
||
+ | local requiredFrameNums = requiredFrameNumData[arg] |
||
− | for tArg in mw.text.gsplit( template, '\n|' ) do |
||
+ | if requiredFrameNums and arg ~= 'Output' then |
||
− | i = i + 1 |
||
+ | for frameNum in pairs( requiredFrameNums ) do |
||
− | if tArg ~= '' then |
||
+ | -- Have to use pairs as it contains a non-sequential set of numbers |
||
− | local key = argList[i] |
||
+ | -- so we have to skip over the extra data in the table |
||
− | tArgs[key] = tArg |
||
+ | if frameNum ~= 'count' then |
||
+ | hasRelevantFrames = true |
||
+ | relevantFrameNums[frameNum] = true |
||
end |
end |
||
end |
end |
||
+ | relevantFrameNums.count = math.max( |
||
− | local craftingArgs = { |
||
+ | requiredFrameNums.count or 0, |
||
− | tArgs[1] or tArgs.A1 or '', tArgs[2] or tArgs.B1 or '', tArgs[3] or tArgs.C1 or '', |
||
+ | relevantFrameNums.count or 0 |
||
− | 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 '', |
||
+ | end |
||
− | Output = tArgs.Output |
||
+ | end |
||
− | } |
||
+ | |||
− | |||
+ | return hasRelevantFrames and relevantFrameNums |
||
− | local expandedFrames = {} |
||
+ | end |
||
− | local hasIngredient |
||
+ | |||
− | local argsWithIngredient = {} |
||
+ | --[[Loops through the relevant frame numbers and extracts them |
||
− | local argGroups = {} |
||
+ | into a new table, taking care of moving any alias references |
||
− | for i, v in pairs( craftingArgs ) do |
||
+ | and cleaning up any unnecessary subframes |
||
− | if v ~= '' then |
||
+ | --]] |
||
− | if aliases then |
||
+ | function p.extractRelevantFrames( relevantFrameNums, frames ) |
||
− | expandedFrames[i] = {} |
||
+ | local relevantFrames = { randomise = frames.randomise } |
||
− | local expandedFrame = {} |
||
+ | local newFrameNum = 1 |
||
− | for frame in mw.text.gsplit( v, '%s*;%s*' ) do |
||
+ | for frameNum, frame in ipairs( frames ) do |
||
− | local parts = slot.getParts( frame ) |
||
+ | local relevantFrame = relevantFrameNums == true or relevantFrameNums[frameNum] |
||
− | local alias = aliases[parts.name] |
||
− | + | if relevantFrame then |
|
+ | if not frame[1] then |
||
− | local expandedAlias = slot.expandAlias( parts, alias ):gsub( '%s*([%[%]:,;])%s*', '%1' ) |
||
+ | local alias = frames.aliasReference and frames.aliasReference[frameNum] |
||
− | expandedFrames[i][frame] = expandedAlias:gsub( '([%(%)])', '%%%1' ) |
||
+ | local moveAlias = true |
||
− | table.insert( expandedFrame, expandedAlias ) |
||
+ | if alias and relevantFrameNums ~= true then |
||
− | else |
||
+ | for i = frameNum, alias.length do |
||
− | table.insert( expandedFrame, frame ) |
||
+ | if not relevantFrameNums[i] then |
||
− | end |
||
+ | moveAlias = false |
||
+ | break |
||
end |
end |
||
− | |||
− | v = table.concat( expandedFrame, ';' ) |
||
− | craftingArgs[i] = v |
||
end |
end |
||
+ | end |
||
− | if i ~= 'Output' then |
||
+ | if alias and moveAlias then |
||
− | local delimitedFrames = ';' .. v .. ';' |
||
+ | if not relevantFrames.aliasReference then |
||
− | for ingredientNum, ingredient in pairs( ingredients ) do |
||
+ | relevantFrames.aliasReference = {} |
||
− | 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 |
||
+ | relevantFrames.aliasReference[newFrameNum] = alias |
||
end |
end |
||
end |
end |
||
+ | relevantFrames[newFrameNum] = frame |
||
− | if hasIngredient then |
||
+ | newFrameNum = newFrameNum + 1 |
||
− | if tArgs.description then |
||
+ | end |
||
− | showDescription = true |
||
− | + | end |
|
+ | |||
− | |||
+ | -- Move frames in subframe to main frames, if the subframe |
||
− | if hasIngredient == 'animated' then |
||
+ | -- is the only frame |
||
− | if tArgs.arggroups then |
||
+ | if not relevantFrames[2] and relevantFrames[1][1] then |
||
− | for argGroup in mw.text.gsplit( tArgs.arggroups, '%s*;%s*' ) do |
||
+ | relevantFrames = relevantFrames[1] |
||
− | local group = {} |
||
+ | end |
||
− | local _, frameCount |
||
+ | |||
− | for arg in mw.text.gsplit( argGroup, '%s*,%s*' ) do |
||
+ | return relevantFrames |
||
− | if arg:match( '^%d$' ) then |
||
+ | end |
||
− | arg = tonumber( arg ) |
||
− | end |
||
− | 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' |
||
+ | --[[Works out what data is relevant to the requested ingredients |
||
− | local found = false |
||
+ | |||
− | for i, v in ipairs( templates ) do |
||
+ | If the template contains any of the ingredients, returns it with any |
||
− | if compareTables( v, tArgs ) then |
||
+ | necessary modifications, and with the crafting arguments parsed |
||
− | found = true |
||
+ | --]] |
||
− | break |
||
+ | function p.processTemplate( tArgs, ingredientPatterns ) |
||
− | end |
||
+ | 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 |
||
+ | else |
||
− | if not found then |
||
+ | modified = true |
||
− | table.insert( templates, tArgs ) |
||
+ | 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 |
||
+ | -- Convert arguments back to shapeless format if they were originally |
||
− | return '[[Category:Empty crafting usage]]' |
||
+ | 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 |
end |
||
+ | tArgs.Output = newCArgs.Output |
||
+ | tArgs.parsed = true |
||
+ | -- Let Module:Recipe table generate these |
||
− | templates[1].head = '1' |
||
+ | -- with the modified crafting args |
||
− | templates[1].showname = '1' |
||
+ | if modified then |
||
− | if showDescription and args.showdesciption ~= '0' or args.showdesciption == '1' then |
||
+ | tArgs.name = nil |
||
− | templates[1].showdescription = '1' |
||
+ | tArgs.ingredients = nil |
||
end |
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 |
||
+ | 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 |
if not args.continue then |
||
− | templates[ |
+ | templates[templateCount].args.foot = '1' |
end |
end |
||
− | local crafting = require( 'Module:Crafting' ) |
||
local out = {} |
local out = {} |
||
− | for i, |
+ | for i, template in ipairs( templates ) do |
+ | out[i] = ( '<!-- [[' .. template.args['%PAGE%'] .. ']] -->\n' |
||
− | table.insert( out, crafting.table( v ) ) |
||
+ | .. 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 16:15, 6 November 2020
This module implements {{crafting usage}}
.
Dependencies
[view | edit | history | purge]The above documentation is transcluded from Module:Crafting usage/doc.
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
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] = ( '<!-- [[' .. template.args['%PAGE%'] .. ']] -->\n'
.. crafting.table( template.args ) )
end
return table.concat( out, '\n' )
end
return p