Module:Crafting usage: Difference between revisions
CraftSpider (talk | contribs) mNo edit summary |
CraftSpider (talk | contribs) mNo edit summary |
||
Line 436: | Line 436: | ||
local templateCount = #templates | local templateCount = #templates | ||
if templateCount == 0 then | if templateCount == 0 then | ||
return "[[Category:" .. emptyCategory .. "]]" --f:expandTemplate{ title = 'Translation category', args = { i18n.emptyCategory, project = '0' } } | return "[[Category:" .. i18n.emptyCategory .. "]]" --f:expandTemplate{ title = 'Translation category', args = { i18n.emptyCategory, project = '0' } } | ||
end | end | ||
Revision as of 23:54, 14 May 2017
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 "[[Category:" .. i18n.emptyCategory .. "]]" --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