Модуль:Сущность: различия между версиями

Нет описания правки
Нет описания правки
Строка 1: Строка 1:
local p = {}
local p = {}
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs
local BASE_USER = "IanComradeBot/"
local moduleDataCache = {}
local switchModeRegistry = {}
local switchModeOrder = {}


local function trim(s)
local function trim(s)
Строка 8: Строка 12:


local function load_module_data(page)
local function load_module_data(page)
    local baseUser = "IanComradeBot/"
     local moduleName = "Module:" .. BASE_USER .. page .. "/data"
     local moduleName = "Module:" .. baseUser .. page .. "/data"
    if moduleDataCache[moduleName] ~= nil then
        return moduleDataCache[moduleName]
    end
     local ok, data = pcall(mw.loadData, moduleName)
     local ok, data = pcall(mw.loadData, moduleName)
     if not ok then return nil end
     if not ok then
        moduleDataCache[moduleName] = nil
        return nil
    end
    moduleDataCache[moduleName] = data
     return data
     return data
end
end
Строка 34: Строка 44:
     tplStr = tplStr .. "}}"
     tplStr = tplStr .. "}}"
     return tplStr
     return tplStr
end
local function sort_entries_by_priority(entries)
    table.sort(entries, function(a, b)
        if a.priority == b.priority then return a.idx < b.idx end
        return a.priority > b.priority
    end)
end
local function make_source(kind, name, pathName, tplPath)
    return { kind = kind, name = name, pathName = pathName, tplPath = tplPath }
end
local function register_switch_mode(name, cfg)
    switchModeRegistry[name] = cfg or {}
    switchModeOrder[#switchModeOrder + 1] = name
end
local function new_switch_state()
    local state = { keyOrder = {}, keyToTemplates = {}, keySources = {} }
    for _, sw in ipairs(switchModeOrder) do
        state.keyOrder[sw] = {}
        state.keyToTemplates[sw] = {}
        state.keySources[sw] = {}
    end
    return state
end
local function ensure_switch_key(state, sw, key)
    local byKey = state.keyToTemplates[sw]
    if not byKey[key] then
        byKey[key] = {}
        table.insert(state.keyOrder[sw], key)
    end
    return byKey[key]
end
local function add_switch_entry(state, sw, key, entry)
    local bucket = ensure_switch_key(state, sw, key)
    entry.idx = #bucket + 1
    bucket[#bucket + 1] = entry
end
local function collect_tpl_calls(entries)
    local tplCalls = {}
    local sources = {}
    if #entries > 0 then
        sort_entries_by_priority(entries)
        for _, e in ipairs(entries) do
            tplCalls[#tplCalls + 1] = e.tpl
            sources[#sources + 1] = e.source
        end
    end
    return tplCalls, sources
end
end


Строка 75: Строка 139:
     end
     end


     -- типы  
     -- типы
     if merged.tags and #merged.tags > 0 then
     if merged.tags and #merged.tags > 0 then
         table.sort(merged.tags)
         table.sort(merged.tags)
Строка 134: Строка 198:
         local entries = keyToTemplates[callKey] or {}
         local entries = keyToTemplates[callKey] or {}
         if #entries > 0 then
         if #entries > 0 then
             table.sort(entries, function(a, b)
             sort_entries_by_priority(entries)
                if a.priority == b.priority then return a.idx < b.idx end
                return a.priority > b.priority
            end)
             for _, e in ipairs(entries) do
             for _, e in ipairs(entries) do
                 local displayLabel = trim(frame:preprocess(e.tplLabel or "") or "")
                 local displayLabel = trim(frame:preprocess(e.tplLabel or "") or "")
Строка 208: Строка 269:
end
end


local switches = { "card", "title" }
-- Add new output modes by registering config with build_entry/build_preview_entry/render hooks.
local switchConfigs = {
register_switch_mode("card", {
    card = {
    full = true,
        wrapper = cardWrapper,
    build_entry = function(ctx, key)
         fullCard = true
        return {
     },
            tplLabel = makeTplCall(ctx.tplPath, "cardLabel", key, ctx.id, ctx.extra),
     title = {
            tplContent = makeTplCall(ctx.tplPath, "cardContent", key, ctx.id, ctx.extra),
         wrapper = function(key, tplCalls, sources, frame)
            tplTag = makeTplCall(ctx.tplPath, "cardTag", key, ctx.id, ctx.extra),
            return renderTitleBlock(key, tplCalls, sources, true, frame)
            source = ctx.source,
            priority = ctx.priority
        }
    end,
    build_preview_entry = function(ctx, key)
        return {
            tplLabel = makeTplCall(ctx.tplPath, "cardLabel", key, ""),
            tplContent = makeTplCall(ctx.tplPath, "cardContent", key, ""),
            source = ctx.source,
            priority = ctx.priority
         }
     end,
     add_entity_extras = function(state, parsed, ctx)
         if type(parsed) == "table" and parsed.cardTag and parsed.cardTag ~= "" then
            add_switch_entry(state, "card", "cardTag", {
                tplLabel = "",
                tplContent = "",
                tplTag = makeTplCall(ctx.tplPath, "cardTag", "cardTag", ctx.id, ctx.extra),
                source = ctx.source,
                priority = ctx.priority
            })
         end
         end
     }
     end,
}
    render_full = function(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders)
        return cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders)
    end
})
 
register_switch_mode("title", {
    build_entry = function(ctx, key)
        return {
            tpl = makeTplCall(ctx.tplPath, "title", key, ctx.id, ctx.extra),
            source = ctx.source,
            priority = ctx.priority
        }
    end,
    build_preview_entry = function(ctx, key)
        return {
            tpl = makeTplCall(ctx.tplPath, "title", key, ""),
            source = ctx.source,
            priority = ctx.priority
        }
    end,
    render_key = function(frame, key, entries, noHeaders)
        local tplCalls, sources = collect_tpl_calls(entries)
        return renderTitleBlock(key, tplCalls, sources, not noHeaders, frame)
    end
})


local function getTemplateMeta(frame, tplPath)
local function getTemplateMeta(frame, tplPath)
Строка 267: Строка 372:
end
end


local function renderBlocks(frame, switchesTbl, configs, keyOrder, keyToTemplates, keySources, noHeaders, entityId)
local function build_key_filter(args)
    local filter = {}
    filter.blacklist = parseListArg(args.blacklist or "")
    filter.whitelist = parseListArg(args.whitelist or "")
    filter.hasWhitelist = next(filter.whitelist) ~= nil
    return filter
end
 
local function should_include_key(filter, sw, key)
    if filter.hasWhitelist then
        return filter.whitelist[sw] and filter.whitelist[sw][key]
    end
    return not (filter.blacklist[sw] and filter.blacklist[sw][key])
end
 
local function each_csv_value(str, fn)
    if not str or str == "" then
        return
    end
    for item in string.gmatch(str, "[^,]+") do
        local value = trim(item)
        if value ~= "" then
            fn(value)
        end
    end
end
 
local function collect_entity_sets(id, componentDefs, prototypeStoreDefs, ignoreComponents, ignorePrototypes)
    local foundComponents, foundPrototypes = {}, {}
 
    local compList = componentDefs[id]
    if type(compList) == "table" then
        for _, v in ipairs(compList) do
            if type(v) == "string" then
                foundComponents[v] = true
            end
        end
    end
 
    each_csv_value(id, function(name)
        if name ~= id then
            if componentDefs[name] ~= nil then
                foundComponents[name] = true
            elseif prototypeStoreDefs[name] ~= nil then
                foundPrototypes[name] = true
            end
        end
    end)
 
    each_csv_value(ignoreComponents, function(name)
        foundComponents[name] = nil
    end)
 
    each_csv_value(ignorePrototypes, function(name)
        foundPrototypes[name] = nil
    end)
 
    return foundComponents, foundPrototypes
end
 
local function resolve_priority(parsed)
    local basePriority = 1
    if type(parsed) == "table" and parsed.priority ~= nil then
        if type(parsed.priority) == "number" then
            basePriority = parsed.priority
        else
            local pnum = tonumber(parsed.priority)
            if pnum then
                basePriority = pnum
            end
        end
    end
    return basePriority
end
 
local function add_entries_from_meta(state, parsed, ctx, filter, isPreview)
    for _, sw in ipairs(switchModeOrder) do
        local mode = switchModeRegistry[sw] or {}
        local keys
        if type(mode.get_keys) == "function" then
            keys = mode.get_keys(parsed)
        else
            keys = (type(parsed) == "table" and parsed[sw]) or {}
        end
 
        if type(keys) == "table" then
            for _, key in ipairs(keys) do
                if (not filter) or should_include_key(filter, sw, key) then
                    local buildFn = isPreview and (mode.build_preview_entry or mode.build_entry) or mode.build_entry
                    if type(buildFn) == "function" then
                        local entry = buildFn(ctx, key)
                        if entry then
                            add_switch_entry(state, sw, key, entry)
                        end
                    end
                end
            end
        end
 
        if (not isPreview) and type(mode.add_entity_extras) == "function" then
            mode.add_entity_extras(state, parsed, ctx)
        end
    end
end
 
local function build_missing_template_error(kind, name, isStore, tplPath)
    local baseType = (kind and (kind:sub(1, 1):upper() .. kind:sub(2)) or "")
    local classType = baseType
    if isStore then
        classType = classType .. "Store"
    end
    local className = name .. baseType
    local tplLabel = "Template:" .. tplPath
    return "{{сущность/infobox/base|тип=" .. classType .. "|название=" .. className .. "|ссылка=" .. tplLabel .. "}}"
end
 
local function renderBlocks(frame, state, noHeaders, entityId)
     local outLocal = {}
     local outLocal = {}
     for _, sw in ipairs(switchesTbl) do
     for _, sw in ipairs(switchModeOrder) do
         local cfg = configs[sw] or {}
         local mode = switchModeRegistry[sw] or {}
         if cfg.fullCard and sw == "card" then
         if mode.full then
            local outStr = cfg.wrapper(frame, keyOrder[sw], keyToTemplates[sw], keySources[sw], entityId, noHeaders)
            local outStr = ""
            if type(mode.render_full) == "function" then
                outStr = mode.render_full(frame, state.keyOrder[sw], state.keyToTemplates[sw], state.keySources[sw],
                    entityId, noHeaders)
            end
             if outStr and outStr ~= "" then table.insert(outLocal, outStr) end
             if outStr and outStr ~= "" then table.insert(outLocal, outStr) end
         else
         else
             for _, key in ipairs(keyOrder[sw] or {}) do
             for _, key in ipairs(state.keyOrder[sw] or {}) do
                 local entries = keyToTemplates[sw][key] or {}
                 local entries = state.keyToTemplates[sw][key] or {}
                local tplCalls = {}
                 if type(mode.render_key) == "function" then
                local sources = {}
                     local outStr = mode.render_key(frame, key, entries, noHeaders)
                 if #entries > 0 then
                    table.sort(entries, function(a, b)
                        if a.priority == b.priority then return a.idx < b.idx end
                        return a.priority > b.priority
                    end)
                    for _, e in ipairs(entries) do
                        table.insert(tplCalls, e.tpl)
                        table.insert(sources, e.source)
                    end
                end
                if noHeaders and sw == "title" then
                     local outStr = renderTitleBlock(key, tplCalls, sources, false, frame)
                     if outStr and outStr ~= "" then table.insert(outLocal, outStr) end
                     if outStr and outStr ~= "" then table.insert(outLocal, outStr) end
                else
                    if cfg.wrapper then
                        local outStr = cfg.wrapper(key, tplCalls, sources, frame)
                        if outStr and outStr ~= "" then table.insert(outLocal, outStr) end
                    end
                 end
                 end
             end
             end
Строка 309: Строка 517:
     if id == "" then return "" end
     if id == "" then return "" end


     local blacklist = parseListArg(args.blacklist or "")
     local filter = build_key_filter(args)
    local whitelist = parseListArg(args.whitelist or "")
    local hasWhitelist = next(whitelist) ~= nil


     local ignoreComponents = args.ignoreComponents or args.ignoreComponent or ""
     local ignoreComponents = args.ignoreComponents or args.ignoreComponent or ""
Строка 321: Строка 527:
     if not componentDefs or not prototypeStoreDefs or not componentStoreDefs then return "" end
     if not componentDefs or not prototypeStoreDefs or not componentStoreDefs then return "" end


     local foundComponents, foundPrototypes = {}, {}
     local foundComponents, foundPrototypes = collect_entity_sets(id, componentDefs, prototypeStoreDefs, ignoreComponents,
    local compList = componentDefs[id]
         ignorePrototypes)
    if type(compList) == "table" then
     local state = new_switch_state()
        for _, v in ipairs(compList) do
            if type(v) == "string" then
                foundComponents[v] = true
            end
        end
    end
 
    for name in string.gmatch(id, "[^,]+") do
        local n = trim(name)
        if n ~= "" and n ~= id then
            if componentDefs[n] ~= nil then
                foundComponents[n] = true
            elseif prototypeStoreDefs[n] ~= nil then
                foundPrototypes[n] = true
            end
        end
    end
 
    if ignoreComponents ~= "" then
        for item in string.gmatch(ignoreComponents, "[^,]+") do
            local name = trim(item)
            if name ~= "" then foundComponents[name] = nil end
        end
    end
    if ignorePrototypes ~= "" then
         for item in string.gmatch(ignorePrototypes, "[^,]+") do
            local name = trim(item)
            if name ~= "" then foundPrototypes[name] = nil end
        end
    end
 
     local switchKeyOrder, switchKeyToTemplates, switchKeySources = {}, {}, {}
    for _, sw in ipairs(switches) do
        switchKeyOrder[sw] = {}; switchKeyToTemplates[sw] = {}; switchKeySources[sw] = {}
    end


     local ok, dp = pcall(require, "Module:GetField")
     local ok, dp = pcall(require, "Module:GetField")
     local errors = {}
     local errors = {}
     local function processEntity(kind, name, isStore)
     local function processEntity(kind, name, isStore)
         local pathName = lcfirst(name)
         local pathName = lcfirst(name)
Строка 371: Строка 543:
         local content = load_template_content(tplPath)
         local content = load_template_content(tplPath)
         if not content then
         if not content then
             if hasWhitelist then
             if filter.hasWhitelist then
                 return
                 return
             end
             end
             local baseType = (kind and (kind:sub(1, 1):upper() .. kind:sub(2)) or "")
             errors[#errors + 1] = build_missing_template_error(kind, name, isStore, tplPath)
            local classType = baseType
            if isStore then classType = classType .. "Store" end
            local className = name .. baseType
            local tplLabel = "Template:" .. tplPath
            table.insert(errors,
                "{{сущность/infobox/base|тип=" .. classType .. "|название=" .. className .. "|ссылка=" .. tplLabel .. "}}")
             return
             return
         end
         end


         local parsed = getTemplateMeta(frame, tplPath)
         local parsed = getTemplateMeta(frame, tplPath)
        if type(parsed) ~= "table" then
            parsed = {}
        end


         local extra = ""
         local extra = ""
Строка 392: Строка 561:
         end
         end


         local basePriority = 1
         add_entries_from_meta(state, parsed, {
        if parsed and parsed.priority ~= nil then
            tplPath = tplPath,
             if type(parsed.priority) == "number" then
            id = id,
                basePriority = parsed.priority
             extra = extra,
             else
            source = make_source(kind, name, pathName, tplPath),
                local pnum = tonumber(parsed.priority)
             priority = resolve_priority(parsed)
                if pnum then basePriority = pnum end
        }, filter, false)
            end
    end
        end


        for _, sw in ipairs(switches) do
    for compName in pairs(foundComponents) do
            local keys = parsed[sw] or {}
        processEntity("component", compName, false)
            for _, key in ipairs(keys) do
    end
                local skip = false
    for protoName in pairs(foundPrototypes) do
                if next(whitelist) ~= nil then
         processEntity("prototype", protoName, false)
                    if not (whitelist[sw] and whitelist[sw][key]) then skip = true end
                else
                    if blacklist[sw] and blacklist[sw][key] then skip = true end
                end
                if not skip then
                    if not switchKeyToTemplates[sw][key] then
                        switchKeyToTemplates[sw][key] = {}
                        table.insert(switchKeyOrder[sw], key)
                    end
                    local tplStr
                    local tplLabelStr, tplContentStr, tplTagStr
                    if sw == "card" then
                        tplLabelStr = makeTplCall(tplPath, "cardLabel", key, id, extra)
                        tplContentStr = makeTplCall(tplPath, "cardContent", key, id, extra)
                        tplTagStr = makeTplCall(tplPath, "cardTag", key, id, extra)
                    else
                        tplStr = makeTplCall(tplPath, sw, key, id, extra)
                    end
                    local priority = basePriority
 
                    local entry
                    if sw == "card" then
                        entry = {
                            tplLabel = tplLabelStr,
                            tplContent = tplContentStr,
                            tplTag = tplTagStr,
                            source = { kind = kind, name = name, pathName = pathName, tplPath = tplPath },
                            priority = priority,
                            idx = #switchKeyToTemplates[sw][key] + 1
                        }
                    else
                        entry = {
                            tpl = tplStr,
                            source = { kind = kind, name = name, pathName = pathName, tplPath = tplPath },
                            priority = priority,
                            idx = #switchKeyToTemplates[sw][key] + 1
                        }
                    end
                    table.insert(switchKeyToTemplates[sw][key], entry)
                end
            end
        end
 
         if parsed and parsed.cardTag and parsed.cardTag ~= "" then
            local key = "cardTag"
            if not switchKeyToTemplates.card[key] then
                switchKeyToTemplates.card[key] = {}
                table.insert(switchKeyOrder.card, key)
            end
 
            local tplTagStr = makeTplCall(tplPath, "cardTag", key, id, extra)
            table.insert(switchKeyToTemplates.card[key], {
                tplLabel = "",
                tplContent = "",
                tplTag = tplTagStr,
                source = { kind = kind, name = name, pathName = pathName, tplPath = tplPath },
                priority = basePriority,
                idx = #switchKeyToTemplates.card[key] + 1
            })
        end
     end
     end
    for compName, _ in pairs(foundComponents) do processEntity("component", compName, false) end
    for protoName, _ in pairs(foundPrototypes) do processEntity("prototype", protoName, false) end


     if type(componentStoreDefs) == "table" then
     if type(componentStoreDefs) == "table" then
Строка 490: Строка 596:


     local out = {}
     local out = {}
 
     if #errors > 0 then
     if #errors > 0 then
         table.insert(out, '{{сущность/infobox|' .. table.concat(errors, "\n") .. '}}')
         table.insert(out, '{{сущность/infobox|' .. table.concat(errors, "\n") .. '}}')
     end
     end


     local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources,
     local blocks = renderBlocks(frame, state, filter.hasWhitelist, id)
        hasWhitelist, id)
     for _, b in ipairs(blocks) do
     for _, b in ipairs(blocks) do table.insert(out, b) end
        table.insert(out, b)
    end


     return frame:preprocess(table.concat(out, "\n") .. "[[Категория:Сущности]]")
     return frame:preprocess(table.concat(out, "\n") .. "[[Категория:Сущности]]")
Строка 513: Строка 620:


     local parsed = getTemplateMeta(frame, tplPath) or {}
     local parsed = getTemplateMeta(frame, tplPath) or {}
 
     if type(parsed) ~= "table" then
    local switchKeyOrder, switchKeyToTemplates, switchKeySources = {}, {}, {}
         parsed = {}
     for _, sw in ipairs(switches) do
         switchKeyOrder[sw] = {}; switchKeyToTemplates[sw] = {}; switchKeySources[sw] = {}
     end
     end


     for _, sw in ipairs(switches) do
     local state = new_switch_state()
        local keys = parsed[sw] or {}
    add_entries_from_meta(state, parsed, {
        for idx, key in ipairs(keys) do
        tplPath = tplPath,
            if not switchKeyToTemplates[sw][key] then
        id = "",
                switchKeyToTemplates[sw][key] = {}
        extra = "",
                table.insert(switchKeyOrder[sw], key)
        source = make_source("", tplPath, tplPath, tplPath),
            end
        priority = 1
            local entry
    }, nil, true)
            if sw == "card" then
                entry = {
                    tplLabel = makeTplCall(tplPath, "cardLabel", key, ""),
                    tplContent = makeTplCall(tplPath, "cardContent", key, ""),
                    source = { kind = "", name = tplPath, pathName = tplPath, tplPath = tplPath },
                    priority = 1,
                    idx = #switchKeyToTemplates[sw][key] + 1
                }
            else
                entry = {
                    tpl = makeTplCall(tplPath, sw, key, ""),
                    source = { kind = "", name = tplPath, pathName = tplPath, tplPath = tplPath },
                    priority = 1,
                    idx = #switchKeyToTemplates[sw][key] + 1
                }
            end
            table.insert(switchKeyToTemplates[sw][key], entry)
        end
    end


     local whitelist = parseListArg(args.whitelist or "")
     local whitelist = parseListArg(args.whitelist or "")
     local hasWhitelist = next(whitelist) ~= nil
     local hasWhitelist = next(whitelist) ~= nil
     local out = {}
     local out = {}
     local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources,
     local blocks = renderBlocks(frame, state, hasWhitelist, "")
        hasWhitelist, "")
     for _, b in ipairs(blocks) do
     for _, b in ipairs(blocks) do table.insert(out, b) end
        table.insert(out, b)
    end


     return frame:preprocess(table.concat(out, "\n"))
     return frame:preprocess(table.concat(out, "\n"))