Модуль:Сущность: различия между версиями
Pok (обсуждение | вклад) Нет описания правки |
Pok (обсуждение | вклад) Нет описания правки |
||
| Строка 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 moduleName = "Module:" .. BASE_USER .. page .. "/data" | |||
local moduleName = "Module:" .. | 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 | ||
sort_entries_by_priority(entries) | |||
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 | ||
-- Add new output modes by registering config with build_entry/build_preview_entry/render hooks. | |||
register_switch_mode("card", { | |||
full = true, | |||
build_entry = function(ctx, key) | |||
return { | |||
tplLabel = makeTplCall(ctx.tplPath, "cardLabel", key, ctx.id, ctx.extra), | |||
tplContent = makeTplCall(ctx.tplPath, "cardContent", key, ctx.id, ctx.extra), | |||
tplTag = makeTplCall(ctx.tplPath, "cardTag", key, ctx.id, ctx.extra), | |||
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 | 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( | for _, sw in ipairs(switchModeOrder) do | ||
local | local mode = switchModeRegistry[sw] or {} | ||
if | if mode.full then | ||
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 {} | ||
if type(mode.render_key) == "function" then | |||
local outStr = mode.render_key(frame, key, entries, noHeaders) | |||
if | |||
local outStr = | |||
if outStr and outStr ~= "" then table.insert(outLocal, outStr) end | if outStr and outStr ~= "" then table.insert(outLocal, outStr) end | ||
end | end | ||
end | end | ||
| Строка 309: | Строка 517: | ||
if id == "" then return "" end | if id == "" then return "" end | ||
local | local filter = build_key_filter(args) | ||
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, | ||
ignorePrototypes) | |||
local state = new_switch_state() | |||
local | |||
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 | ||
errors[#errors + 1] = build_missing_template_error(kind, name, isStore, tplPath) | |||
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 | ||
add_entries_from_meta(state, parsed, { | |||
tplPath = tplPath, | |||
id = id, | |||
extra = extra, | |||
source = make_source(kind, name, pathName, tplPath), | |||
priority = resolve_priority(parsed) | |||
}, filter, false) | |||
end | |||
for compName in pairs(foundComponents) do | |||
processEntity("component", compName, false) | |||
end | |||
for protoName in pairs(foundPrototypes) do | |||
processEntity("prototype", protoName, false) | |||
end | 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, | local blocks = renderBlocks(frame, state, filter.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 | |||
parsed = {} | |||
end | end | ||
local state = new_switch_state() | |||
add_entries_from_meta(state, parsed, { | |||
tplPath = tplPath, | |||
id = "", | |||
extra = "", | |||
source = make_source("", tplPath, tplPath, tplPath), | |||
priority = 1 | |||
}, nil, true) | |||
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, | local blocks = renderBlocks(frame, state, 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")) | ||