Модуль:Сущность: различия между версиями
Материал из Space Station 14 Вики
Pok (обсуждение | вклад) мНет описания правки |
Pok (обсуждение | вклад) мНет описания правки |
||
| Строка 555: | Строка 555: | ||
if vStr ~= "" then | if vStr ~= "" then | ||
if outputType == "enum" then | if outputType == "enum" then | ||
table.insert(out, | table.insert(out, vStr .. " " .. keyStr) | ||
else | else | ||
table.insert(out, bullet .. keyStr .. sep .. vStr) | table.insert(out, bullet .. keyStr .. sep .. vStr) | ||
Версия от 14:57, 4 марта 2026
Для документации этого модуля может быть создана страница Модуль:Сущность/doc
local p = {}
local getArgs = require('Module:Arguments').getArgs
local function trim(s)
if not s then return s end
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
local function load_module_data(page)
local baseUser = "IanComradeBot/"
local moduleName = "Module:" .. baseUser .. page .. "/data"
local ok, data = pcall(mw.loadData, moduleName)
if not ok then return nil end
return data
end
local function load_template_content(path)
local title = mw.title.new("Template:" .. path)
if not title then return nil end
local ok, content = pcall(function() return title:getContent() end)
if not ok then return nil end
return content
end
local function lcfirst(s)
if not s or s == "" then return s end
return string.lower(s:sub(1, 1)) .. (s:sub(2) or "")
end
local function makeTplCall(tplPath, sw, key, id, extra)
local tplStr = "{{" .. tplPath .. "|" .. sw .. "|" .. key
tplStr = tplStr .. "|id=" .. tostring(id)
if extra and extra ~= "" then tplStr = tplStr .. "|" .. extra end
tplStr = tplStr .. "}}"
return tplStr
end
local function makeSourceLink(s)
local className = s.name .. (s.kind and (s.kind:sub(1, 1):upper() .. s.kind:sub(2)) or "")
local tplLabel = "Template:" .. s.tplPath
return "[[" .. tplLabel .. "|" .. className .. "]]"
end
local function renderTitleBlock(key, tplCalls, sources, includeHeader, frame)
local parts = {}
if tplCalls and #tplCalls > 0 then
for i, tpl in ipairs(tplCalls) do
local add = true
if frame then
local expanded = frame:preprocess(tpl)
add = expanded and trim(expanded) ~= ""
end
if add then
local src = sources and sources[i]
local line = '<div>' .. tpl .. '</div><div class="ts-Сущность-field">' .. makeSourceLink(src) .. '</div>'
table.insert(parts, '<div class="ts-Сущность">' .. line .. '</div>')
end
end
if #parts == 0 then
return ""
end
if includeHeader then
table.insert(parts, 1, "<h2>" .. mw.text.encode(key) .. "</h2>")
end
end
return table.concat(parts, "\n")
end
local function buildCardCall(merged, entityId)
local parts = {}
-- id сущности
if entityId and entityId ~= "" then
parts[#parts + 1] = "id=" .. mw.text.encode(entityId)
end
-- типы (cardTag)
if merged.tags and #merged.tags > 0 then
table.sort(merged.tags)
parts[#parts + 1] = "тип=" .. table.concat(merged.tags, ", ")
end
-- секции карточки
if merged.sections and #merged.sections > 0 then
table.sort(merged.sections, function(a, b)
if a == "Сущность" then return true end
if b == "Сущность" then return false end
return a < b
end)
parts[#parts + 1] = "sections=" .. table.concat(merged.sections, ", ")
for _, section in ipairs(merged.sections) do
local labels = merged.labelLists[section]
if labels and #labels > 0 then
local enc = {}
for i = 1, #labels do
enc[i] = mw.text.encode(labels[i])
end
parts[#parts + 1] = mw.text.encode(section) .. "=" .. table.concat(enc, ", ")
end
end
end
-- содержимое полей
for compositeKey, displayLabel in pairs(merged.labelOverrides or {}) do
if displayLabel and displayLabel ~= "" then
parts[#parts + 1] = mw.text.encode(compositeKey .. "_label") .. "=" .. displayLabel
end
end
for compositeKey, content in pairs(merged.contentByKey or {}) do
if content and content ~= "" then
parts[#parts + 1] = mw.text.encode(compositeKey) .. "=" .. mw.text.encode(content)
end
end
if #parts == 0 then
return ""
end
return "{{карточка/сущность|" .. table.concat(parts, "|") .. "}}"
end
local function cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId)
local merged = {
sections = {},
sectionsMap = {},
labelLists = {},
labelSets = {},
labelOverrides = {},
contentByKey = {},
tags = {},
tagSet = {}
}
for _, callKey in ipairs(keyOrder or {}) do
local entries = keyToTemplates[callKey] or {}
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
local displayLabel = trim(frame:preprocess(e.tplLabel or "") or "")
local content = trim(frame:preprocess(e.tplContent or "") or "")
local compositeKey = (callKey:find("_", 1, true)) and callKey or ("Сущность_" .. callKey)
local section = (callKey:find("_", 1, true)) and callKey:match("^([^_]+)") or "Сущность"
if displayLabel ~= "" or content ~= "" then
if not merged.sectionsMap[section] then
merged.sectionsMap[section] = true
table.insert(merged.sections, section)
end
merged.labelOverrides[compositeKey] = displayLabel
merged.contentByKey[compositeKey] = content
merged.labelSets[section] = merged.labelSets[section] or {}
if not merged.labelSets[section][compositeKey] then
merged.labelSets[section][compositeKey] = true
local cur = merged.labelLists[section] or {}
table.insert(cur, compositeKey)
merged.labelLists[section] = cur
end
end
if e.cardTag and e.cardTag ~= "" then
if not merged.tagSet[e.cardTag] then
merged.tagSet[e.cardTag] = true
table.insert(merged.tags, e.cardTag)
end
end
end
end
end
return buildCardCall(merged, entityId)
end
local switches = { "card", "title" }
local switchConfigs = {
card = {
wrapper = cardWrapper,
fullCard = true
},
title = {
wrapper = function(key, tplCalls, sources, frame)
return renderTitleBlock(key, tplCalls, sources, true, frame)
end
}
}
local function getTemplateMeta(frame, tplPath)
local expanded = frame:expandTemplate {
title = tplPath,
args = { "json" }
}
local ok, data = pcall(mw.text.jsonDecode, expanded)
if ok and type(data) == "table" then
return data
end
return ""
end
local function parseListArg(str)
local res = {}
if not str or str == "" then return res end
for item in string.gmatch(str, "[^,]+") do
local s = trim(item)
if s ~= "" then
local a, b = s:match("^([^_]+)_(.+)$")
if a and b then
res[a] = res[a] or {}
res[a][b] = true
end
end
end
return res
end
local function renderBlocks(frame, switchesTbl, configs, keyOrder, keyToTemplates, keySources, noHeaders, entityId)
local outLocal = {}
for _, sw in ipairs(switchesTbl) do
local cfg = configs[sw] or {}
if cfg.fullCard and sw == "card" then
local outStr = cfg.wrapper(frame, keyOrder[sw], keyToTemplates[sw], keySources[sw], entityId)
if outStr and outStr ~= "" then table.insert(outLocal, outStr) end
else
for _, key in ipairs(keyOrder[sw] or {}) do
local entries = keyToTemplates[sw][key] or {}
local tplCalls = {}
local sources = {}
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
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
return outLocal
end
function p.get(frame)
local args = getArgs(frame, { removeBlanks = false })
local id = args[1] or ""
if id == "" then return "" end
local blacklist = parseListArg(args.blacklist or "")
local whitelist = parseListArg(args.whitelist or "")
local hasWhitelist = next(whitelist) ~= nil
local componentDefs = load_module_data("component.json")
local prototypeDefs = load_module_data("prototype.json")
if not componentDefs or not prototypeDefs then return "" end
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
local protoEntry = prototypeDefs[id]
if type(protoEntry) == "table" then
for protoName, _ in pairs(protoEntry) do
if type(protoName) == "string" then
foundPrototypes[protoName] = true
end
end
end
for name in string.gmatch(id, "[^,]+") do
local n = trim(name)
if n ~= "" then
if componentDefs[n] ~= nil then foundComponents[n] = true end
if componentDefs[n] == nil and prototypeDefs[n] == nil then foundComponents[n] = true 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 errors = {}
local function processEntity(kind, name)
local pathName = lcfirst(name)
local tplPath = kind .. "/" .. pathName
local content = load_template_content(tplPath)
if not content then
if hasWhitelist then
return
end
local classType = (kind and (kind:sub(1, 1):upper() .. kind:sub(2)) or "")
local className = name .. classType
local tplLabel = "Template:" .. tplPath
table.insert(errors,
"{{сущность/infobox|тип=" .. classType .. "|название=" .. className .. "|ссылка=" .. tplLabel .. "}}")
return
end
local parsed = getTemplateMeta(frame, tplPath)
for _, sw in ipairs(switches) do
local keys = parsed[sw] or {}
for _, key in ipairs(keys) do
local skip = false
if next(whitelist) ~= nil then
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 extra = ""
if ok and dp and dp.flattenField then
local dataPage = tplPath .. ".json"
extra = dp.flattenField({ args = { id, dataPage } })
end
local tplStr
local tplLabelStr, tplContentStr
if sw == "card" then
tplLabelStr = makeTplCall(tplPath, "cardLabel", key, id, extra)
tplContentStr = makeTplCall(tplPath, "cardContent", key, id, extra)
else
tplStr = makeTplCall(tplPath, sw, key, id, extra)
end
local priority = 1
if parsed and parsed.priority ~= nil then
if type(parsed.priority) == "number" then
priority = parsed.priority
else
local pnum = tonumber(parsed.priority)
if pnum then priority = pnum end
end
end
local entry
if sw == "card" then
entry = {
tplLabel = tplLabelStr,
tplContent = tplContentStr,
cardTag = parsed.cardTag,
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
end
local items = {}
for compName, _ in pairs(foundComponents) do table.insert(items, { kind = "component", name = compName }) end
for protoName, _ in pairs(foundPrototypes) do table.insert(items, { kind = "prototype", name = protoName }) end
for _, it in ipairs(items) do processEntity(it.kind, it.name) end
local out = {}
for _, e in ipairs(errors) do table.insert(out, e) end
local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources,
hasWhitelist, id)
for _, b in ipairs(blocks) do table.insert(out, b) end
return frame:preprocess(table.concat(out, "\n"))
end
function p.preview(frame)
local args = getArgs(frame, { removeBlanks = false })
local tplPath = args[1] or ""
if tplPath == "" then return "" end
local content = load_template_content(tplPath)
if not content then
return ""
end
local parsed = getTemplateMeta(frame, tplPath) or {}
local switchKeyOrder, switchKeyToTemplates, switchKeySources = {}, {}, {}
for _, sw in ipairs(switches) do
switchKeyOrder[sw] = {}; switchKeyToTemplates[sw] = {}; switchKeySources[sw] = {}
end
for _, sw in ipairs(switches) do
local keys = parsed[sw] or {}
for idx, key in ipairs(keys) do
if not switchKeyToTemplates[sw][key] then
switchKeyToTemplates[sw][key] = {}
table.insert(switchKeyOrder[sw], key)
end
local entry
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 hasWhitelist = next(whitelist) ~= nil
local out = {}
local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources,
hasWhitelist, "")
for _, b in ipairs(blocks) do table.insert(out, b) end
return frame:preprocess(table.concat(out, "\n"))
end
local function is_array(tbl)
local max = 0
local count = 0
for k in pairs(tbl) do
if type(k) ~= "number" then
return false
end
if k > max then max = k end
count = count + 1
end
return count > 0 and max == count
end
local function apply_pattern(s, pattern, repl)
if not pattern or pattern == "" or not s then
return s
end
local text = tostring(s)
local replacement
if repl and repl ~= "" then
replacement = tostring(repl)
replacement = replacement:gsub("\\(%d)", "%%%1")
else
replacement = "%1"
end
local patt = pattern
if not patt:find("%^") and not patt:find("%$") then
patt = "^" .. patt .. "$"
end
return (text:gsub(patt, replacement))
end
function p.jsonList(frame)
local args = getArgs(frame, { removeBlanks = false })
local jsonStr = mw.text.unstripNoWiki(trim(args[1] or args.json or ""))
if jsonStr == "" then return "" end
local ok, data = pcall(mw.text.jsonDecode, jsonStr)
if not ok or type(data) ~= "table" then
return ""
end
local outputType = (args.type or "list"):lower()
local bullet = mw.text.unstripNoWiki(args.prefix or "* ")
local sep = mw.text.unstripNoWiki(args.sep or ": ")
local keyPattern = mw.text.unstripNoWiki(args.key_pattern or "(.*)")
local keyReplace = mw.text.unstripNoWiki(args.key_replace or "\\1")
local valuePattern = mw.text.unstripNoWiki(args.value_pattern or "(.*)")
local valueReplace = mw.text.unstripNoWiki(args.value_replace or "\\1")
local out = {}
if is_array(data) then
for _, v in ipairs(data) do
local text = ""
if type(v) == "table" then
if is_array(v) then
text = table.concat(v, ", ")
else
local okJson, jsonVal = pcall(mw.text.jsonEncode, v)
if okJson and jsonVal then
text = jsonVal
end
end
else
text = tostring(v)
end
if text ~= "" then
local patt = valuePattern ~= "" and valuePattern or keyPattern
local repl = valueReplace ~= "" and valueReplace or keyReplace
text = apply_pattern(text, patt, repl)
if outputType == "enum" then
table.insert(out, text)
else
table.insert(out, bullet .. text)
end
end
end
else
local keys = {}
for k in pairs(data) do
keys[#keys + 1] = k
end
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
for _, k in ipairs(keys) do
local v = data[k]
local vStr
if type(v) == "table" then
local okJson, jsonVal = pcall(mw.text.jsonEncode, v)
if okJson and jsonVal then
vStr = jsonVal
else
vStr = ""
end
else
vStr = tostring(v)
end
local keyStr = tostring(k)
keyStr = apply_pattern(keyStr, keyPattern, keyReplace)
vStr = apply_pattern(vStr, valuePattern, valueReplace)
if vStr ~= "" then
if outputType == "enum" then
table.insert(out, vStr .. " " .. keyStr)
else
table.insert(out, bullet .. keyStr .. sep .. vStr)
end
end
end
end
if outputType == "enum" then
return frame:preprocess(table.concat(out, ", "))
else
return frame:preprocess(table.concat(out, "\n"))
end
end
return p