Перейти к содержанию

Модуль:Таблица рецептов

Материал из LemonCraft Wiki
Версия от 10:42, 26 марта 2025; Spark108 (обсуждение | вклад) (1 версия импортирована)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

Для документации этого модуля может быть создана страница Модуль:Таблица рецептов/doc

---------------------------------------------------
-- Модуль для создания таблиц рецептов в статьях
---------------------------------------------------

local m = {}

-- Интернационализация и локализация
local i18n = {
    -- Параметры заголовка
    headingDescription = "Описание",
    headingIngredients = "Ингредиенты",
    headingName = "Результат",
    headingRecipe = "Рецепты [[$1|$2]]",
    headingRecipeAlt = "Рецепты для [[$1|$2]]",
    headingRecipeFallback = "Процесс",
    
    -- Вспомогательные модули
    moduleSlot = [[Модуль:Инвентарный слот]],
    moduleUi = [[Модуль:Интерфейс]],
    moduleUtils = [[Модуль:Специальные утилиты]],
    moduleMods = [[Модуль:Модификации]],
    
    -- Разделители
    separator = ' +',
    setSeparator = " или",
    
    -- Описание таблицы в HTML-атрибуте data-description
    tableDescription = "Рецепты $2",
    tableDescriptionAlt = "Рецепты для $2",
    tableDescriptionFallback = "Рецепты обработки"
}

-- Зависимости
local slot = require(i18n.moduleSlot)
local prefixesMatch = slot.i18n.prefixesMatch
local mergeList = require(i18n.moduleUtils).mergeList
local modIds = mw.loadData(i18n.moduleMods)

-- Служебная функция: Преобразует все аргументы входа и выхода, сохраняя
-- все фреймы в единую таблицу, и сохраняя ссылки на псевдонимы.
-- Одинаковые слоты повторно используют одну и ту же таблицу, что позволяет
-- сравнивать их подобно строкам.
local function parseRecipeArgs(args, ingredientArgVals, outputArgs, defaultMod)
    -- Аргументы в исходном виде
    local recipeArgs = {}
    for _, arg in pairs(ingredientArgVals) do
        recipeArgs[arg] = args[arg]
    end
    for _, arg in pairs(outputArgs) do
        recipeArgs[arg] = args[arg]
    end
    
    -- Обработка аргументов
    local parsedFrameInput = {}
    local parsedRecipeArgs = {}
    for arg, frameInput in pairs(recipeArgs) do
        if frameInput then -- есть содержимое аргумента?
            local randomise
            for _, oArg in pairs(outputArgs) do
                if arg == oArg then
                    -- Отключаем рандомизацию для аргументов выхода
                    randomise = 'никогда'
                    break
                end
            end
            
            -- Нормализация
            local rawFrames = frameInput
            if not frameInput[1] then
            	rawFrames = slot.convertFrameText(frameInput, defaultMod)
            end
            local frameText = slot.stringifyFrames(rawFrames)
            
            local frames = not randomise and parsedFrameInput[frameText]
            if not frames then
                frames = slot.expandAliases(rawFrames, randomise, true)
                parsedFrameInput[frameText] = frames
            end
            parsedRecipeArgs[arg] = frames
        end
    end
    
    return parsedRecipeArgs
end

-- Создаёт ссылку с выносом префикса за её пределы
function m.prefixedLink(name, mod)
    -- Ищем и выносим префиксы
    local prefix
    for _, prefixMatch in pairs(prefixesMatch) do
        prefix = mw.ustring.match(name, '^' .. prefixMatch .. ' ') or ''
        if prefix ~= '' then
            name = name:sub(#prefix + 1)
            
            -- Повторяем процесс, если есть ещё префикс «невощёный»
            local unwaxed = mw.ustring.match(name, '^' .. prefixesMatch.unwaxed .. ' ')
            if unwaxed then
                prefix = prefix .. unwaxed
                name = name:sub(#unwaxed + 1)
            end
            break
        end
    end
    
    -- Формируем цель ссылки
    local page = name
    if mod then
    	local mod = modIds[mod] or mod
    	page = mw.ustring.gsub(name, '^%l', mw.ustring.upper)
        page = slot.i18n.modLink:gsub('%$1', mod):gsub('%$2', page)
    end
    
    -- Формируем и возвращаем ссылку
    return table.concat{ prefix, '[[', page, '|', name, ']]' }
end

-- Преобразует множество слотов во множества предметов, соответствующих
-- фреймам тех слотов, используя исходное название псевдонима, если оно есть.
function m.makeItemSets(argVals, parsedArgs)
    -- Уже зарегистрированные фреймы
    local usedItems = {}
    
    -- вспомогательная функция
    local function addItem(items, arg, frame, alias)
        if alias then -- псевдоним
            frame = alias["фрейм"]
        end
        
        -- Уникальное название фрейма
        local uniqName = (frame.mod or '') .. ':' .. frame.name
        if not usedItems[uniqName] then
            usedItems[uniqName] = true
            items[#items + 1] = frame
        end
        
        return alias and alias["длина"] or 1
    end
    
    -- Множества предметов
    local itemSets = {}
    local i = 1
    for _, arg in ipairs(argVals) do
        local frames = parsedArgs[arg]
        if frames then
            -- Предметы во множестве
            local items = {}
            local frameNum = 1
            while frameNum <= #frames do
                -- Цикл while используется, так как могут проскакиваться несколько фреймов,
                -- принадлежащих псевдониму
                local frame = frames[frameNum]
                if frame[1] then
                    -- контейнер подфреймов
                    local subframeNum = 1
                    while subframeNum <= #frame do
                        local subframe = frame[subframeNum]
                        if subframe.name ~= '' then
                            local alias = frame["ссылканапсевдонимы"] and frame["ссылканапсевдонимы"][subframeNum]
                            subframeNum = subframeNum + addItem(items, arg, subframe, alias)
                        else
                            subframeNum = subframeNum + 1
                        end
                    end
                    frameNum = frameNum + 1
                elseif frame.name ~= '' then
                    local alias = frames["ссылканапсевдонимы"] and frames["ссылканапсевдонимы"][frameNum]
                    frameNum = frameNum + addItem(items, arg, frame, alias)
                else
                    frameNum = frameNum + 1
                end
            end
            
            -- Добавляем множество, если оно не пустое
            if #items > 0 then
                itemSets[i] = items
                i = i + 1
            end
        end
    end
    
    return itemSets
end

-- Создаёт по множествам предметов ссылки для колонок названий и ингредиентов
-- с использованием разделителей и возможным удалением префиксов «любой» и
-- «соответствующий».
function m.makeItemLinks(itemSets, removePrefixes)
    local links = {}
    for i, itemSet in ipairs(itemSets) do
        local linkSet = {}
        for i2, item in ipairs(itemSet) do
            local name = item.name
            if removePrefixes then
                -- Удаляем префиксы
                name = mw.ustring.gsub(name, '^' .. prefixesMatch.any .. ' ', '')
                name = mw.ustring.gsub(name, '^' .. prefixesMatch.matching .. ' ', '')
                
                -- Переводим первую букву в заглавную форму
                name = mw.ustring.gsub(name, '^%l', mw.ustring.upper)
            end
            
            -- Формируем ссылку.
            -- Если она содержит слово «или», то разбиваем её.
            local disjunctionA, disjunctionB = mw.ustring.match(name, "(.-) или (.+)")
			if disjunctionA then
				linkSet[i2] = m.prefixedLink(disjunctionA, item.mod) 
				    .. ' или ' 
				    .. m.prefixedLink(disjunctionB, item.mod)
			else
				linkSet[i2] = m.prefixedLink(name, item.mod)
			end
        end
        links[i] = table.concat(linkSet, i18n.setSeparator .. '<br>')
    end
    
    return table.concat(links, i18n.separator .. '<br>')
end

-- Создать заглавие таблицы
function m.makeHeader(recipeSettings, class, showName, showDescription, multirow, multipleRecipeTypes)
    -- CSS-класс
    class = class or ''
    
    -- Колонка с названиями
    local nameCell = ''
    if showName then
        nameCell = '!!' .. i18n.headingName
    end
    
    -- Колонка с описанием
    local descriptionCell = ''
    if showDescription then
        descriptionCell = '!!class="unsortable"|' .. i18n.headingDescription
    end
    
    local recipeAttribs = ''
    if multirow then
        -- несколько колонок
        class = 'sortable' .. class
        recipeAttribs = 'class="unsortable"|'
    end
    
    -- Заголовок процесса
    local headingRecipe = i18n.headingRecipeFallback
    local processLinkPrefix
    if recipeSettings["модификация"] then
    	processLinkPrefix = recipeSettings["модификация"] .. '/'
    else
    	processLinkPrefix = ''
    end
    
    if not multipleRecipeTypes then
	    if (recipeSettings["тип"] or recipeSettings["обработчик"]) and recipeSettings["типа"] then
	    	local processLink = processLinkPrefix .. (recipeSettings["тип"] or recipeSettings["обработчик"])
	    	headingRecipe = i18n.headingRecipe:gsub('%$1', processLink)
	                                          :gsub('%$2', recipeSettings["типа"])
	    elseif recipeSettings["обработчик"] and recipeSettings["обработчика"] then
	    	headingRecipe = i18n.headingRecipeAlt:gsub('%$1', processLinkPrefix .. recipeSettings["обработчик"])
	    	                                     :gsub('%$2', recipeSettings["обработчика"])
	    end
    end
    
    local tableDescription = i18n.tableDescriptionFallback
    if not multipleRecipeTypes then
	    if recipeSettings["типа"] then
	    	tableDescription = i18n.tableDescription:gsub('%$2', recipeSettings["типа"])
	    elseif recipeSettings["обработчика"] then
	    	tableDescription = i18n.tableDescriptionAlt:gsub('%$2', recipeSettings["обработчика"])
	    end
	end
    
    local header = table.concat({
        ' {| class="wikitable recipe-table collapsible ' .. class .. '" data-description="' .. tableDescription .. '"',
        '!' .. i18n.headingIngredients .. '!!' ..
        recipeAttribs .. headingRecipe ..
        nameCell .. descriptionCell
    }, '\n')
    return header
end

-- Создать содержимое для ячейки с названием
function m.makeNameCell(name, outputArgs, parsedRecipeArgs)
    return name or m.makeItemLinks(m.makeItemSets(outputArgs, parsedRecipeArgs), true)
end

-- Создать содержимое для ячейки с ингредиентами
function m.makeIngredientsCell(ingredients, itemSets)
    return ingredients or m.makeItemLinks(itemSets)
end

-- Собственно создать таблицу (входная точка модуля).
-- Использует переменные DPL для группирования нескольких вызовов в одну таблицу.
-- Также возвращает список уникальных ингредиентов, который может использоваться
-- для обеспечения работы шаблона «Использование для крафта».
function m.table(args, settings)
    local f = mw.getCurrentFrame()
    
    -- продолжаем ли таблицу?
    local multirow = f:callParserFunction( '#dplvar', 'recipetable-multirow' )
    if multirow == '' then
        multirow = nil
    end
    
    -- Параметры главы и подвала таблицы
    local showHead = args["глава"]
    local showFoot = args["подвал"]
    if multirow then
        -- игнорируем параметр главы, если таблица продолжается
        showHead = nil
    elseif showHead and not showFoot then
        -- начало таблицы
        multirow = true
        f:callParserFunction('#dplvar:set', 'recipetable-multirow', '1')
    else
        -- единичный ряд таблицы
        showHead = true
        showFoot = true
    end
    
    -- Контроль отображения названия или описания
    local showName = args["показатьназвание"] or args["показатьимя"]
    local showDescription = args["показатьописание"]
    if multirow then
        if showHead then
            -- Указываем, что нужно добавить колонку с описанием
            showName = showName or '1'
            f:callParserFunction('#dplvar:set', 'recipetable-name', showName, 'recipetable-description', showDescription)
        else
            showName = f:callParserFunction('#dplvar', 'recipetable-name')
            showDescription = f:callParserFunction('#dplvar', 'recipetable-description')
        end
    end
    if showName ~= '1' then
        showName = nil
    end
    if showDescription == '' then
        showDescription = nil
    end
    
    -- Таблица или её часть
    local out = {}
    
    -- Заголовок
    if showHead then
        out[1] = m.makeHeader(settings, args["класс"], showName, showDescription, multirow, args["несколько обработок"])
    end
    
    -- Разбор аргументов
    local ingredientArgVals = settings["аргументы ингредиентов"]
    local outputArgs = settings["аргументы выхода"]
    
    local parsedRecipeArgs = args
    local defaultMod = args["мод"] or args["Мод"] or settings["модификация"]
    if not args["обработанный"] then
        parsedRecipeArgs = parseRecipeArgs(args, ingredientArgVals, outputArgs, defaultMod)
    end
    
    -- Клетки
    local cells = {}
    
    -- Ингредиенты
    local ingredientsItemSets = m.makeItemSets(ingredientArgVals, parsedRecipeArgs)
    cells[1] = '|' .. m.makeIngredientsCell(args["ингредиенты"], ingredientsItemSets)
    
    -- Собственно обработка
    local funcUi = settings["функция интерфейса"]
    if type(funcUi) == "string" then
    	local moduleUi = settings["модуль интерфейса"] or i18n.moduleUi
    	funcUi = require(moduleUi)[funcUi]
    elseif type(funcUi) ~= "function" then
		error("Необходима ссылка на функцию интерфейса или её название в модуле")
	end
    cells[2] = '|style="text-align:center"|' .. funcUi(args)
    
    -- Название
    if showName then
        cells[#cells + 1] = '|class="recipe-table-name"|' .. m.makeNameCell(args["название"], outputArgs, parsedRecipeArgs)
    end
    
    -- Описание
    if showDescription then
        cells[#cells + 1] = '|' .. ( args["описание"] or '' )
    end
    
    out[#out + 1] = table.concat( cells, '\n' )
    
    -- Подвал
    out[#out + 1] = showFoot and '|}' or ''
    if showFoot then
        f:callParserFunction('#dplvar:set',
            'recipetable-multirow', '',
            'recipetable-name', '',
            'recipetable-description', ''
        )
    end
    
    return table.concat( out, '\n|-\n' ), ingredientsItemSets
end

return m