Minecraft Wiki
편집 요약 없음
잔글 (일본판 적용시도)
30번째 줄: 30번째 줄:
 
local prefixes = slot.i18n.prefixes
 
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
 
--[[Escapes special characters in ingredient names, and returns the correct
 
pattern depending on the match type
 
pattern depending on the match type
76번째 줄: 53번째 줄:
 
local escaped = Reverselink.xlink( ingredient:gsub( '([^%w])', patternChars ) )
 
local escaped = Reverselink.xlink( ingredient:gsub( '([^%w])', patternChars ) )
 
if not matchTypes then
 
if not matchTypes then
patterns[i] = '%z' .. escaped .. '%z'
+
patterns[i] = '^' .. escaped .. '$'
 
else
 
else
 
local matchType = matchTypes[i] or matchTypes
 
local matchType = matchTypes[i] or matchTypes
 
if matchType == 'start' then
 
if matchType == 'start' then
patterns[i] = '%z' .. escaped
+
patterns[i] = '^' .. escaped
 
elseif matchType == 'end' then
 
elseif matchType == 'end' then
patterns[i] = escaped .. '%z'
+
patterns[i] = escaped .. '$'
 
else
 
else
 
patterns[i] = escaped
 
patterns[i] = escaped
146번째 줄: 123번째 줄:
   
 
-- 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
164번째 줄: 139번째 줄:
 
required frame numbers, or true if all of them are required
 
required frame numbers, or true if all of them are required
 
--]]
 
--]]
local function findRequiredFrameNums( parsedCArgs, ingredientPatterns )
+
local function findRequiredFrameNums( parsedCArgs, ingredientPatterns )
local requiredFrameNums = {}
+
local requiredFrameNums = {}
local hasRequiredFrames
+
local hasRequiredFrames
for arg, frames in pairs( parsedCArgs ) do
+
for arg, frames in pairs( parsedCArgs ) do
if arg ~= 'Output' then
+
if arg ~= 'Output' then
local requiredFrames = {}
+
local requiredFrames = {}
local count = 0
+
local count = 0
for i, frame in ipairs( frames ) do
+
for i, frame in ipairs( frames ) do
 
if containsIngredients( frame.name or frame[1].name, ingredientPatterns ) then
-- Guess what? If we only take the first we lose the subframes.
 
 
requiredFrames[i] = true
-- And then 'Cobblestone or Blackstone' starts breaking.
 
 
count = count + 1
local tframe = frame[1] and flatten(frame) or { frame }
 
 
end
local names = '\0' .. table.concat(map(tframe, function (fr) return type(fr) == 'table' and fr.name or '' end), '\0') .. '\0'
 
 
end
if containsIngredients( names, ingredientPatterns ) then
 
 
if count > 0 then
requiredFrames[i] = true
 
count = count + 1
+
if count == #frames then
end
+
return true
end
+
end
 
if count > 0 then
 
 
hasRequiredFrames = true
if count == #frames then
 
  +
requiredFrames.count = count
return true
 
 
requiredFrameNums[arg] = requiredFrames
end
 
+
end
 
end
hasRequiredFrames = true
 
 
end
requiredFrames.count = count
 
 
requiredFrameNums[arg] = requiredFrames
 
 
return hasRequiredFrames and requiredFrameNums
end
 
end
 
end
 
 
return hasRequiredFrames and requiredFrameNums
 
 
end
 
end
   
399번째 줄: 370번째 줄:
 
end
 
end
   
--[[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 )
--]]
 
 
return mw.getCurrentFrame():callParserFunction( '#dpl:', {
local function dplQueryWrapper( category, ignore )
 
 
category = category,
local data = {}
 
 
nottitleregexp = ignore,
if type(category) == 'string' then
 
 
include = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ),
category = mw.text.split(category, '|')
 
 
mode = 'userformat',
else
 
 
secseparators = '====',
assert(type(category) == 'table')
 
 
multisecseparators = '===='
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
 
end
   
446번째 줄: 399번째 줄:
 
 
 
local data
 
local data
if args.category then
+
if args.category then
data = dplQueryWrapper( args.category, args.ignore )
+
data = dplQuery( args.category, args.ignore )
else
+
else
  +
-- DPL has a limit of four categories, so do it in chunks
-- Need to format the catparts
 
 
data = {}
local catParts = text.split( i18n.queryCategory, '%$1' )
 
local cats = map(ingredients, function (s)
+
local dataNum = 1
  +
local ingredientCount = #ingredients
return catParts[1] .. s .. catParts[2]
 
 
local catParts = text.split( i18n.queryCategory, '%$1' )
end)
 
 
for i = 1, ingredientCount, 4 do
data = dplQueryWrapper( cats, args.ignore )
 
  +
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
 
 

2020년 9월 16일 (수) 08:39 판

이 모듈은 {{crafting usage}}를 구현한다.

제작법의 탐색은 "~을(를) 사용하는 제작법"에 분류되는 아이템들을 탐색하여 제작법을 가져온다. (흑마법!)

의존 관계

위 모듈에 의해 정의되는 "Recipe using ~" 분류를 탐색하여 어떤 아이템이 해당 재료를 제작재료로 사용하는지를 알아낸다. 따라서 위 모듈이 생성하는 분류 이름에 모듈:Crafting usage의 코드를 맞추어야 한다.

[보기 | 편집 | 역사 | 캐시 제거]위 설명문서는 모듈:Crafting usage/doc에서 왔습니다.
local p = {}

local i18n = {
	emptyCategory = '빈 제작법 사용',
	moduleCrafting = [[모듈:Crafting]],
	moduleSlot = [[모듈:Inventory slot]],
	moduleText = [[모듈:Text]],
	queryCategory = '$1을(를) 사용하는 제작법',
	templateCrafting = 'Crafting',
}
p.i18n = i18n

local Autolink = require( [[모듈:Autolink]] )
local Reverselink = require( [[모듈:Reverselink]] )

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

--[[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 = Reverselink.xlink( ingredient:gsub( '([^%w])', patternChars ) )
		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 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 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
--]]
function dplQuery( category, ignore )
	return mw.getCurrentFrame():callParserFunction( '#dpl:', {
		category = category,
		nottitleregexp = ignore,
		include = '{' .. i18n.templateCrafting .. '}:' .. table.concat( argList, ':' ),
		mode = 'userformat',
		secseparators = '====',
		multisecseparators = '===='
	} )
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 = 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 = 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
	
	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