Module:Crafting usage: Difference between revisions
Trying to support the Metallurgic infuser for crafting usage sections. |
applied fixes from copiliot |
||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
-- Add DPL validation | |||
local function isDPLAvailable(f) | |||
local success = pcall(function() | |||
f:callParserFunction('#dpl', {'category=Test'}) | |||
end) | |||
return success | |||
end | |||
-- Add input validation with DPL check | |||
local function validateArgs(f, args) | |||
if not args then | |||
error('Arguments table is required') | |||
end | |||
if not isDPLAvailable(f) then | |||
error('DynamicPageList extension is not properly configured. Please contact wiki administrator.') | |||
end | |||
return true | |||
end | |||
-- Cache frequently used functions | |||
local text_split = mw.text.split | |||
local text_gsplit = mw.text.gsplit | |||
-- Add error handling and performance improvements | |||
function p.dpl(f) | |||
-- Validate input and DPL availability | |||
local args = f:getParent().args | |||
validateArgs(f, args) | |||
-- Use local references for better performance | |||
local grid = require('Module:Grid') | |||
local ingredients = args[1] and text_split(args[1], '%s*,%s*') or {mw.title.getCurrentTitle().text} | |||
-- Add type checking | |||
if type(ingredients) ~= 'table' then | |||
error('Invalid ingredients format') | |||
end | |||
local matchTypes = args.match and args.match:find( ',' ) and text_split( args.match, '%s*,%s*' ) or args.match | |||
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' | |||
} | |||
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 } | |||
local data = '' | |||
if args.category then | |||
-- Add error handling for DPL calls | |||
local success, result = pcall(function() | |||
return f:callParserFunction('#dpl', { | |||
category = args.category, | |||
nottitleregexp = args.ignore, | |||
include = '{Crafting}:' .. table.concat(argList, ':'), | |||
mode = 'userformat', | |||
secseparators = '====', | |||
multisecseparators = '====', | |||
'' | |||
}) | |||
end) | |||
if not success then | |||
return string.format( | |||
"[[Category:DPL errors]]Error processing DPL: %s", | |||
mw.text.nowiki(result) | |||
) | |||
end | |||
data = result | |||
else | |||
-- Add chunked DPL calls with error handling | |||
for i = 1, #ingredients, 4 do | |||
local success, result = pcall(function() | |||
return f:callParserFunction('#dpl', { | |||
category = 'Recipe using ' .. table.concat(ingredients, '|Recipe using ', i, math.min(i + 3, #ingredients)), | |||
nottitleregexp = args.ignore, | |||
include = '{Crafting}:' .. table.concat(argList, ':'), | |||
mode = 'userformat', | |||
secseparators = '====', | |||
multisecseparators = '====', | |||
'' | |||
}) | |||
end) | |||
if not success then | |||
return string.format( | |||
"[[Category:DPL errors]]Error processing DPL chunk %d: %s", | |||
i, | |||
mw.text.nowiki(result) | |||
) | |||
end | |||
data = data .. result | |||
end | |||
end | |||
-- Comment this next line out if you're not using any aliases | |||
local aliases = mw.loadData( 'Module:Grid/Aliases' ) | |||
-- Add documentation | |||
---@param ingredient string The ingredient to match | |||
---@param ingredientNum number The ingredient number | |||
---@return string pattern The regex pattern | |||
local function matchPattern( ingredient, ingredientNum ) | |||
local matchType = matchTypes | |||
if type( matchType ) == 'table' then | |||
matchType = matchTypes[ingredientNum] | |||
end | |||
local pattern | |||
local escaped = ingredient:gsub( '([%(%)])', '%%%1' ) | |||
if matchType == 'start' then | |||
pattern = '[;:%]]%s*' .. escaped | |||
elseif matchType == 'end' then | |||
pattern = escaped .. '%s*[,;%[]' | |||
elseif matchType == 'any' then | |||
pattern = escaped | |||
else | |||
pattern = '[;:%]]%s*' .. escaped .. '%s*[,;%[]' | |||
end | |||
return pattern | |||
end | |||
-- Add performance optimization for table comparison | |||
local function compareTables( a, b ) | |||
if not a or not b then return false end | |||
for k, v in pairs( a ) do | |||
if type( b[k] ) ~= type( v ) then | |||
return false | |||
end | |||
if type( v ) == 'table' then | |||
if not compareTables( v, b[k] ) then | |||
return false | |||
end | |||
elseif v ~= b[k] then | |||
return false | |||
end | |||
end | |||
for k, v in pairs( b ) do | |||
if a[k] == nil then | |||
return false | |||
end | |||
end | |||
return true | |||
end | |||
-- Add result caching for frequently accessed data | |||
local cache = setmetatable({}, {__mode = 'kv'}) | |||
local function getCachedResult(key, fn, ...) | |||
if not cache[key] then | |||
cache[key] = fn(...) | |||
end | |||
return cache[key] | |||
end | |||
local out = {} | |||
local showDesciption | |||
local templates = {} | |||
for template in text_gsplit( data, '====' ) do | |||
-- If ignoreusage is empty | |||
if template:find( '^%s*|' ) then | |||
local tArgs = {} | |||
local i = 0 | |||
-- Extract the arguments from the DPL query | |||
for tArg in text_gsplit( template, '\n|' ) do | |||
i = i + 1 | |||
if tArg ~= '' then | |||
local key = argList[i] | |||
tArgs[key] = tArg | |||
end | |||
end | |||
local craftingArgs = { | |||
tArgs[1] or tArgs.A1 or '' or 'Infuse', tArgs[2] or tArgs.B1 or '' or 'Input', 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 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 text_gsplit( tArgs.arggroups, '%s*;%s*' ) do | |||
local group = {} | |||
local _, frameCount | |||
for arg in 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 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 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 | |||
for i, v in ipairs( templates ) do | |||
if compareTables( v, tArgs ) then | |||
found = true | |||
break | |||
end | |||
end | |||
if not found then | |||
table.insert( templates, tArgs ) | |||
end | |||
end | |||
end | |||
end | |||
-- Add error handling for empty results | |||
if #templates == 0 then | |||
return '[[Category:Empty crafting usage]]' | |||
end | |||
templates[1].head = '1' | |||
templates[1].showname = '1' | |||
if showDescription and args.showdesciption ~= '0' or args.showdesciption == '1' then | |||
templates[1].showdescription = '1' | |||
end | |||
if not args.continue then | |||
templates[#templates].foot = '1' | |||
end | |||
local crafting = require( 'Module:Crafting' ) | |||
local out = {} | |||
for i, v in ipairs( templates ) do | |||
table.insert( out, crafting.table(v) ) | |||
end | |||
return table.concat( out, '\n' ) | |||
end | end | ||
return p | return p |
Revision as of 08:20, 5 January 2025
Documentation for this module may be created at Module:Crafting usage/doc
local p = {}
-- Add DPL validation
local function isDPLAvailable(f)
local success = pcall(function()
f:callParserFunction('#dpl', {'category=Test'})
end)
return success
end
-- Add input validation with DPL check
local function validateArgs(f, args)
if not args then
error('Arguments table is required')
end
if not isDPLAvailable(f) then
error('DynamicPageList extension is not properly configured. Please contact wiki administrator.')
end
return true
end
-- Cache frequently used functions
local text_split = mw.text.split
local text_gsplit = mw.text.gsplit
-- Add error handling and performance improvements
function p.dpl(f)
-- Validate input and DPL availability
local args = f:getParent().args
validateArgs(f, args)
-- Use local references for better performance
local grid = require('Module:Grid')
local ingredients = args[1] and text_split(args[1], '%s*,%s*') or {mw.title.getCurrentTitle().text}
-- Add type checking
if type(ingredients) ~= 'table' then
error('Invalid ingredients format')
end
local matchTypes = args.match and args.match:find( ',' ) and text_split( args.match, '%s*,%s*' ) or args.match
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'
}
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 }
local data = ''
if args.category then
-- Add error handling for DPL calls
local success, result = pcall(function()
return f:callParserFunction('#dpl', {
category = args.category,
nottitleregexp = args.ignore,
include = '{Crafting}:' .. table.concat(argList, ':'),
mode = 'userformat',
secseparators = '====',
multisecseparators = '====',
''
})
end)
if not success then
return string.format(
"[[Category:DPL errors]]Error processing DPL: %s",
mw.text.nowiki(result)
)
end
data = result
else
-- Add chunked DPL calls with error handling
for i = 1, #ingredients, 4 do
local success, result = pcall(function()
return f:callParserFunction('#dpl', {
category = 'Recipe using ' .. table.concat(ingredients, '|Recipe using ', i, math.min(i + 3, #ingredients)),
nottitleregexp = args.ignore,
include = '{Crafting}:' .. table.concat(argList, ':'),
mode = 'userformat',
secseparators = '====',
multisecseparators = '====',
''
})
end)
if not success then
return string.format(
"[[Category:DPL errors]]Error processing DPL chunk %d: %s",
i,
mw.text.nowiki(result)
)
end
data = data .. result
end
end
-- Comment this next line out if you're not using any aliases
local aliases = mw.loadData( 'Module:Grid/Aliases' )
-- Add documentation
---@param ingredient string The ingredient to match
---@param ingredientNum number The ingredient number
---@return string pattern The regex pattern
local function matchPattern( ingredient, ingredientNum )
local matchType = matchTypes
if type( matchType ) == 'table' then
matchType = matchTypes[ingredientNum]
end
local pattern
local escaped = ingredient:gsub( '([%(%)])', '%%%1' )
if matchType == 'start' then
pattern = '[;:%]]%s*' .. escaped
elseif matchType == 'end' then
pattern = escaped .. '%s*[,;%[]'
elseif matchType == 'any' then
pattern = escaped
else
pattern = '[;:%]]%s*' .. escaped .. '%s*[,;%[]'
end
return pattern
end
-- Add performance optimization for table comparison
local function compareTables( a, b )
if not a or not b then return false end
for k, v in pairs( a ) do
if type( b[k] ) ~= type( v ) then
return false
end
if type( v ) == 'table' then
if not compareTables( v, b[k] ) then
return false
end
elseif v ~= b[k] then
return false
end
end
for k, v in pairs( b ) do
if a[k] == nil then
return false
end
end
return true
end
-- Add result caching for frequently accessed data
local cache = setmetatable({}, {__mode = 'kv'})
local function getCachedResult(key, fn, ...)
if not cache[key] then
cache[key] = fn(...)
end
return cache[key]
end
local out = {}
local showDesciption
local templates = {}
for template in text_gsplit( data, '====' ) do
-- If ignoreusage is empty
if template:find( '^%s*|' ) then
local tArgs = {}
local i = 0
-- Extract the arguments from the DPL query
for tArg in text_gsplit( template, '\n|' ) do
i = i + 1
if tArg ~= '' then
local key = argList[i]
tArgs[key] = tArg
end
end
local craftingArgs = {
tArgs[1] or tArgs.A1 or '' or 'Infuse', tArgs[2] or tArgs.B1 or '' or 'Input', 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 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 text_gsplit( tArgs.arggroups, '%s*;%s*' ) do
local group = {}
local _, frameCount
for arg in 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 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 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
for i, v in ipairs( templates ) do
if compareTables( v, tArgs ) then
found = true
break
end
end
if not found then
table.insert( templates, tArgs )
end
end
end
end
-- Add error handling for empty results
if #templates == 0 then
return '[[Category:Empty crafting usage]]'
end
templates[1].head = '1'
templates[1].showname = '1'
if showDescription and args.showdesciption ~= '0' or args.showdesciption == '1' then
templates[1].showdescription = '1'
end
if not args.continue then
templates[#templates].foot = '1'
end
local crafting = require( 'Module:Crafting' )
local out = {}
for i, v in ipairs( templates ) do
table.insert( out, crafting.table(v) )
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.