Alswo96284 (토론 | 기여) 편집 요약 없음 |
Minejango2 (토론 | 기여) 잔글 (일본판 적용시도) |
||
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) |
||
⚫ | |||
− | 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) |
||
⚫ | |||
− | table.insert(newtbl, v) |
||
⚫ | |||
⚫ | |||
− | 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] = ' |
+ | 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] = ' |
+ | patterns[i] = '^' .. escaped |
elseif matchType == 'end' then |
elseif matchType == 'end' then |
||
− | patterns[i] = escaped .. ' |
+ | 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 |
⚫ | |||
− | -- 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 count == #frames then |
|
− | + | return true |
|
− | + | end |
|
⚫ | |||
⚫ | |||
⚫ | |||
− | if count == #frames then |
||
+ | requiredFrames.count = count |
||
− | return true |
||
⚫ | |||
⚫ | |||
− | + | end |
|
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
− | end |
||
− | |||
⚫ | |||
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. |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
− | if type(category) == 'string' then |
||
⚫ | |||
− | category = mw.text.split(category, '|') |
||
⚫ | |||
− | else |
||
⚫ | |||
− | assert(type(category) == 'table') |
||
⚫ | |||
− | end |
||
+ | } ) |
||
− | |||
⚫ | |||
− | local j = 1 |
||
− | local count = #category |
||
⚫ | |||
⚫ | |||
− | local catSlice = table.concat(category, '|', i, math.min(i + 3, count)) |
||
⚫ | |||
⚫ | |||
⚫ | |||
− | include = includeStr, |
||
⚫ | |||
⚫ | |||
⚫ | |||
− | }) |
||
− | j = j + 1 |
||
− | end |
||
− | |||
⚫ | |||
end |
end |
||
446번째 줄: | 399번째 줄: | ||
local data |
local data |
||
− | if args.category then |
+ | if args.category then |
− | data = |
+ | 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 |
||
⚫ | |||
− | local catParts = text.split( i18n.queryCategory, '%$1' ) |
||
− | local |
+ | local dataNum = 1 |
+ | local ingredientCount = #ingredients |
||
⚫ | |||
⚫ | |||
− | end) |
||
⚫ | |||
− | data = dplQueryWrapper( cats, args.ignore ) |
||
+ | data[dataNum] = dplQuery( |
||
+ | catParts[1] .. table.concat( |
||
+ | ingredients, |
||
⚫ | |||
+ | i, |
||
+ | math.min( i + 3, ingredientCount ) |
||
+ | ) .. catParts[2], |
||
+ | args.ignore |
||
⚫ | |||
+ | dataNum = dataNum + 1 |
||
⚫ | |||
⚫ | |||
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