Minecraft Wiki
(Keep track of duplicate templates)
m (Revert)
(15 intermediate revisions by 5 users not shown)
Line 5: Line 5:
 
moduleCrafting = [[Module:Crafting]],
 
moduleCrafting = [[Module:Crafting]],
 
moduleSlot = [[Module:Inventory slot]],
 
moduleSlot = [[Module:Inventory slot]],
  +
moduleText = [[Module:Text]],
 
queryCategory = 'Recipe using $1',
 
queryCategory = 'Recipe using $1',
 
templateCrafting = 'Crafting',
 
templateCrafting = 'Crafting',
Line 10: Line 11:
 
p.i18n = i18n
 
p.i18n = i18n
   
  +
local text = require( i18n.moduleText )
 
local slot = require( i18n.moduleSlot )
 
local slot = require( i18n.moduleSlot )
 
local crafting = require( i18n.moduleCrafting )
 
local crafting = require( i18n.moduleCrafting )
Line 21: Line 23:
 
'A3title', 'A3link', 'B3title', 'B3link', 'C3title', 'C3link',
 
'A3title', 'A3link', 'B3title', 'B3link', 'C3title', 'C3link',
 
'Otitle', 'Olink',
 
'Otitle', 'Olink',
  +
'%PAGE%',
 
}
 
}
 
local prefixes = slot.i18n.prefixes
 
local prefixes = slot.i18n.prefixes
   
  +
local function map(tbl, func)
--[[Escapes the parenthesis in ingredient names, and returns the correct
 
  +
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
 
pattern depending on the match type
 
--]]
 
--]]
 
local function createIngredientPatterns( ingredients, matchTypes )
 
local function createIngredientPatterns( ingredients, matchTypes )
  +
local patternChars = {
  +
['^'] = '%^';
  +
['$'] = '%$';
  +
['('] = '%(';
  +
[')'] = '%)';
  +
['%'] = '%%';
  +
['.'] = '%.';
  +
['['] = '%[';
  +
[']'] = '%]';
  +
['*'] = '%*';
  +
['+'] = '%+';
  +
['-'] = '%-';
  +
['?'] = '%?';
  +
['\0'] = '%z';
 
}
 
local patterns = {}
 
local patterns = {}
 
for i, ingredient in ipairs( ingredients ) do
 
for i, ingredient in ipairs( ingredients ) do
local escaped = ingredient:gsub( '([()])', '%%%1' )
+
local escaped = ingredient:gsub( '([^%w])', patternChars )
 
if not matchTypes then
 
if not matchTypes then
patterns[i] = '^' .. escaped .. '$'
+
patterns[i] = '%z' .. escaped .. '%z'
 
else
 
else
 
local matchType = matchTypes[i] or matchTypes
 
local matchType = matchTypes[i] or matchTypes
 
if matchType == 'start' then
 
if matchType == 'start' then
patterns[i] = '^' .. escaped
+
patterns[i] = '%z' .. escaped
 
elseif matchType == 'end' then
 
elseif matchType == 'end' then
patterns[i] = escaped .. '$'
+
patterns[i] = escaped .. '%z'
 
else
 
else
 
patterns[i] = escaped
 
patterns[i] = escaped
Line 66: Line 108:
 
local tArgs = {}
 
local tArgs = {}
 
local i = 1
 
local i = 1
for arg in mw.text.gsplit( template, '\n|' ) do
+
for arg in text.gsplit( template, '\n|' ) do
 
if arg ~= '' then
 
if arg ~= '' then
 
tArgs[argList[i]] = arg
 
tArgs[argList[i]] = arg
Line 102: Line 144:
   
 
-- Loops through the wanted ingredients, and checks if the name contains it
 
-- 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 )
 
local function containsIngredients( name, ingredientPatterns )
 
for _, ingredient in pairs( ingredientPatterns ) do
 
for _, ingredient in pairs( ingredientPatterns ) do
Line 126: Line 170:
 
local count = 0
 
local count = 0
 
for i, frame in ipairs( frames ) do
 
for i, frame in ipairs( frames ) do
  +
-- Guess what? If we only take the first we lose the subframes.
if containsIngredients( frame.name or frame[1].name, ingredientPatterns ) then
 
  +
-- 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
 
requiredFrames[i] = true
 
count = count + 1
 
count = count + 1
Line 153: Line 201:
 
if predefinedArgGroups or '' ~= '' then
 
if predefinedArgGroups or '' ~= '' then
 
local i = 1
 
local i = 1
for argGroup in mw.text.gsplit( predefinedArgGroups, '%s*;%s*' ) do
+
for argGroup in text.gsplit( predefinedArgGroups, '%s*;%s*' ) do
 
local groupData = { args = {} }
 
local groupData = { args = {} }
for arg in mw.text.gsplit( argGroup, '%s*,%s*' ) do
+
for arg in text.gsplit( argGroup, '%s*,%s*' ) do
 
arg = tonumber( arg ) or arg
 
arg = tonumber( arg ) or arg
 
if not groupData.count then
 
if not groupData.count then
Line 350: Line 398:
   
 
--[[Performs the DPL query which retrieves the arguments from the crafting
 
--[[Performs the DPL query which retrieves the arguments from the crafting
templates on the pages within the requested categories
+
templates on the pages within the requested categories.
  +
If more than four categories are given, break them down into batches of four.
 
--]]
 
--]]
function dplQuery( category, ignore )
+
local function dplQueryWrapper( category, ignore )
 
local data = {}
return mw.getCurrentFrame():callParserFunction( '#dpl:', {
 
category = category,
+
if type(category) == 'string' then
  +
category = mw.text.split(category, '|')
nottitleregexp = ignore,
 
  +
else
include = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ),
 
  +
assert(type(category) == 'table')
mode = 'userformat',
 
  +
end
secseparators = '====',
 
  +
multisecseparators = '===='
 
 
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
 
end
  +
   
 
--[[The main body, which retrieves the data, and returns the relevant
 
--[[The main body, which retrieves the data, and returns the relevant
Line 373: Line 440:
 
f = mw.getCurrentFrame()
 
f = mw.getCurrentFrame()
 
end
 
end
local ingredients = args[1] and mw.text.split( args[1], '%s*,%s*' ) or { mw.title.getCurrentTitle().text }
+
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 mw.text.split( args.match, '%s*,%s*' ) or args.match
+
local matchTypes = args.match and args.match:find( ',' ) and text.split( args.match, '%s*,%s*' ) or args.match
 
local ingredientPatterns = createIngredientPatterns( ingredients, matchTypes )
 
local ingredientPatterns = createIngredientPatterns( ingredients, matchTypes )
 
 
 
local data
 
local data
 
if args.category then
 
if args.category then
data = dplQuery( args.category, args.ignore )
+
data = dplQueryWrapper( args.category, args.ignore )
 
else
 
else
  +
-- Need to format the catparts
-- DPL has a limit of four categories, so do it in chunks
 
  +
local catParts = text.split( i18n.queryCategory, '%$1' )
data = {}
 
local dataNum = 1
+
local cats = map(ingredients, function (s)
 
return catParts[1] .. s .. catParts[2]
local ingredientCount = #ingredients
 
  +
end)
local catParts = mw.text.split( i18n.queryCategory, '%$1' )
 
  +
data = dplQueryWrapper( cats, args.ignore )
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
 
end
 
 
Line 404: Line 459:
 
local templates = {}
 
local templates = {}
 
local i = 1
 
local i = 1
for templateArgs in mw.text.gsplit( data:sub( 5 ), '====' ) do
+
for templateArgs in text.gsplit( data:sub( 5 ), '====' ) do
 
local tArgs = extractArgs( templateArgs )
 
local tArgs = extractArgs( templateArgs )
 
local newArgs = tArgs and p.processTemplate( tArgs, ingredientPatterns )
 
local newArgs = tArgs and p.processTemplate( tArgs, ingredientPatterns )
Line 445: Line 500:
 
local out = {}
 
local out = {}
 
for i, template in ipairs( templates ) do
 
for i, template in ipairs( templates ) do
out[i] = crafting.table( template.args )
+
out[i] = ( '<!-- [[' .. template.args['%PAGE%'] .. ']] -->\n'
  +
.. crafting.table( template.args ) )
 
end
 
end
 
return table.concat( out, '\n' )
 
return table.concat( out, '\n' )

Revision as of 16:15, 6 November 2020

[view | edit | history | purge]DocumentationJump to code ↴

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