Модуль:Сущность: различия между версиями
Pok (обсуждение | вклад) Нет описания правки |
Pok (обсуждение | вклад) Нет описания правки |
||
| (не показано 57 промежуточных версий этого же участника) | |||
| Строка 42: | Строка 42: | ||
end | end | ||
local function renderTitleBlock(key, tplCalls, sources, includeHeader) | local function renderTitleBlock(key, tplCalls, sources, includeHeader, frame) | ||
local parts = {} | local parts = {} | ||
if tplCalls and #tplCalls > 0 then | if tplCalls and #tplCalls > 0 then | ||
for i, tpl in ipairs(tplCalls) do | for i, tpl in ipairs(tplCalls) do | ||
local | local add = true | ||
local src = sources and sources[i] | 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 | ||
end | end | ||
return table.concat(parts, "\n") | 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 | |||
-- типы | |||
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) .. "=" .. content | |||
end | |||
end | |||
if #parts == 0 then | |||
return "" | |||
end | |||
return "{{карточка/сущность|" .. table.concat(parts, "|") .. "}}" | |||
end | |||
local function cardWrapper(frame, keyOrder, keyToTemplates, keySources, entityId, noHeaders) | |||
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 | |||
if displayLabel ~= "" and (not merged.labelOverrides[compositeKey] or merged.labelOverrides[compositeKey] == "") then | |||
merged.labelOverrides[compositeKey] = displayLabel | |||
end | |||
if content ~= "" then | |||
local prev = merged.contentByKey[compositeKey] | |||
if prev and prev ~= "" then | |||
merged.contentByKey[compositeKey] = prev .. "\n" .. content | |||
else | |||
merged.contentByKey[compositeKey] = content | |||
end | |||
end | |||
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 | |||
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 | |||
end | |||
if noHeaders then | |||
local hasLabel = false | |||
for _, v in pairs(merged.labelOverrides or {}) do | |||
if v and v ~= "" then hasLabel = true break end | |||
end | |||
if not hasLabel then | |||
for _, lst in pairs(merged.labelLists or {}) do | |||
if #lst > 0 then hasLabel = true break end | |||
end | |||
end | |||
local hasContent = false | |||
for _, v in pairs(merged.contentByKey or {}) do | |||
if v and v ~= "" then hasContent = true break end | |||
end | |||
if not hasLabel and not hasContent then | |||
return "" | |||
end | |||
end | |||
return buildCardCall(merged, entityId) | |||
end | end | ||
| Строка 59: | Строка 206: | ||
local switchConfigs = { | local switchConfigs = { | ||
card = { | card = { | ||
wrapper = | wrapper = cardWrapper, | ||
fullCard = true | |||
}, | }, | ||
title = { | title = { | ||
wrapper = function(key, tplCalls, sources) | wrapper = function(key, tplCalls, sources, frame) | ||
return renderTitleBlock(key, tplCalls, sources, true) | return renderTitleBlock(key, tplCalls, sources, true, frame) | ||
end | end | ||
} | } | ||
| Строка 108: | Строка 246: | ||
end | end | ||
local function renderBlocks(frame, switchesTbl, configs, keyOrder, keyToTemplates, keySources, noHeaders) | local function renderBlocks(frame, switchesTbl, configs, keyOrder, keyToTemplates, keySources, noHeaders, entityId) | ||
local outLocal = {} | local outLocal = {} | ||
for _, sw in ipairs(switchesTbl) do | for _, sw in ipairs(switchesTbl) do | ||
local cfg = configs[sw] or {} | local cfg = configs[sw] or {} | ||
for _, key in ipairs(keyOrder[sw] or {}) do | if cfg.fullCard and sw == "card" then | ||
local outStr = cfg.wrapper(frame, keyOrder[sw], keyToTemplates[sw], keySources[sw], entityId, noHeaders) | |||
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 | 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 | ||
| Строка 148: | Строка 291: | ||
local whitelist = parseListArg(args.whitelist or "") | local whitelist = parseListArg(args.whitelist or "") | ||
local hasWhitelist = next(whitelist) ~= nil | local hasWhitelist = next(whitelist) ~= nil | ||
local ignoreComponents = args.ignoreComponents or args.ignoreComponent or "" | |||
local ignorePrototypes = args.ignorePrototypes or args.ignorePrototype or "" | |||
local componentDefs = load_module_data("component.json") | local componentDefs = load_module_data("component.json") | ||
local | local prototypeStoreDefs = load_module_data("prototype_store.json") | ||
if not componentDefs or not | local componentStoreDefs = load_module_data("component_store.json") | ||
if not componentDefs or not prototypeStoreDefs or not componentStoreDefs then return "" end | |||
local foundComponents, foundPrototypes = {}, {} | local foundComponents, foundPrototypes = {}, {} | ||
| Строка 163: | Строка 310: | ||
end | end | ||
for name in string.gmatch(id, "[^,]+") do | |||
local n = trim(name) | |||
if n ~= "" and n ~= id then | |||
if | if componentDefs[n] ~= nil then | ||
foundPrototypes[ | foundComponents[n] = true | ||
elseif prototypeStoreDefs[n] ~= nil then | |||
foundPrototypes[n] = true | |||
end | end | ||
end | end | ||
end | end | ||
for | |||
if ignoreComponents ~= "" then | |||
for item in string.gmatch(ignoreComponents, "[^,]+") do | |||
local name = trim(item) | |||
if | 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 | ||
end | end | ||
| Строка 184: | Строка 339: | ||
end | end | ||
local ok, dp = pcall(require, "Module:GetField") | |||
local errors = {} | local errors = {} | ||
local function processEntity(kind, name) | local function processEntity(kind, name, isStore) | ||
local pathName = lcfirst(name) | local pathName = lcfirst(name) | ||
local tplPath = kind .. "/" .. pathName | local tplPath = kind .. "/" .. pathName | ||
if isStore then | |||
tplPath = tplPath .. "/store" | |||
end | |||
local content = load_template_content(tplPath) | local content = load_template_content(tplPath) | ||
if not content then | if not content then | ||
| Строка 193: | Строка 353: | ||
return | return | ||
end | end | ||
local | local baseType = (kind and (kind:sub(1, 1):upper() .. kind:sub(2)) or "") | ||
local className = name .. | local classType = baseType | ||
if isStore then classType = classType .. "Store" end | |||
local className = name .. baseType | |||
local tplLabel = "Template:" .. tplPath | local tplLabel = "Template:" .. tplPath | ||
table.insert(errors, | table.insert(errors, | ||
"{{сущность/infobox|тип=" .. classType .. "|название=" .. className .. "|ссылка=" .. tplLabel .. "}}") | "{{сущность/infobox/base|тип=" .. classType .. "|название=" .. className .. "|ссылка=" .. tplLabel .. "}}") | ||
return | return | ||
end | end | ||
local parsed = getTemplateMeta(frame, tplPath) | local parsed = getTemplateMeta(frame, tplPath) | ||
for _, sw in ipairs(switches) do | for _, sw in ipairs(switches) do | ||
local keys = parsed[sw] or {} | local keys = parsed[sw] or {} | ||
| Строка 221: | Строка 383: | ||
extra = dp.flattenField({ args = { id, dataPage } }) | extra = dp.flattenField({ args = { id, dataPage } }) | ||
end | end | ||
local tplStr = makeTplCall(tplPath, sw, key, id, extra) | 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 | local priority = 1 | ||
if parsed and parsed.priority ~= nil then | if parsed and parsed.priority ~= nil then | ||
| Строка 231: | Строка 400: | ||
end | end | ||
end | end | ||
local entry = { | local entry | ||
tpl = tplStr, | 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) | table.insert(switchKeyToTemplates[sw][key], entry) | ||
end | end | ||
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 | |||
local compStore = componentStoreDefs[id] | |||
if type(compStore) == "table" then | |||
for compName in pairs(compStore) do | |||
processEntity("component", compName, true) | |||
end | end | ||
end | end | ||
end | end | ||
local | if type(prototypeStoreDefs) == "table" then | ||
local protoStore = prototypeStoreDefs[id] | |||
if type(protoStore) == "table" then | |||
for protoName in pairs(protoStore) do | |||
processEntity("prototype", protoName, true) | |||
end | |||
end | |||
end | |||
local out = {} | local out = {} | ||
if #errors > 0 then | |||
table.insert(out, '{{сущность/infobox|' .. table.concat(errors, "\n") .. '}}') | |||
end | |||
local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources, | local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources, | ||
hasWhitelist | hasWhitelist, id) | ||
for _, b in ipairs(blocks) do table.insert(out, b) end | for _, b in ipairs(blocks) do table.insert(out, b) end | ||
return frame:preprocess(table.concat(out, " | return frame:preprocess(table.concat(out, "\n") .. "[[Категория:Сущности]]") | ||
end | end | ||
| Строка 281: | Строка 481: | ||
table.insert(switchKeyOrder[sw], key) | table.insert(switchKeyOrder[sw], key) | ||
end | end | ||
local | 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) | table.insert(switchKeyToTemplates[sw][key], entry) | ||
end | end | ||
| Строка 296: | Строка 506: | ||
local out = {} | local out = {} | ||
local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources, | local blocks = renderBlocks(frame, switches, switchConfigs, switchKeyOrder, switchKeyToTemplates, switchKeySources, | ||
hasWhitelist) | hasWhitelist, "") | ||
for _, b in ipairs(blocks) do table.insert(out, b) end | for _, b in ipairs(blocks) do table.insert(out, b) end | ||
return frame:preprocess(table.concat(out, "\n\n")) | 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.json(frame) | |||
local args = getArgs(frame, { removeBlanks = false }) | |||
local jsonStr = mw.text.unstripNoWiki(trim(args[1] or args.json or "")) | |||
local tplPath = trim(args[2] or args.template or "") | |||
if jsonStr == "" or tplPath == "" then return "" end | |||
local ok, data = pcall(mw.text.jsonDecode, jsonStr) | |||
if not ok or type(data) ~= "table" then | |||
return "" | |||
end | |||
local calls = {} | |||
local function makeCall(id, obj) | |||
if type(id) ~= "string" then return end | |||
local parts = { "{{" .. tplPath, "id=" .. id } | |||
if type(obj) == "table" then | |||
for k, v in pairs(obj) do | |||
if v ~= nil then | |||
parts[#parts + 1] = tostring(k) .. "=" .. tostring(v) | |||
end | |||
end | |||
end | |||
parts[#parts + 1] = "}}" | |||
calls[#calls + 1] = table.concat(parts, "|") | |||
end | |||
if is_array(data) then | |||
for _, item in ipairs(data) do | |||
if type(item) == "table" then | |||
for k, v in pairs(item) do | |||
makeCall(k, v) | |||
end | |||
end | |||
end | |||
else | |||
for k, v in pairs(data) do | |||
makeCall(k, v) | |||
end | |||
end | |||
if #calls == 0 then | |||
return "" | |||
end | |||
local rendered = table.concat(calls, "\n") | |||
return frame:preprocess(rendered) | |||
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 pairPattern = mw.text.unstripNoWiki(args.pattern or "") | |||
local pairReplace = mw.text.unstripNoWiki(args.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) | |||
local line | |||
if outputType == "enum" then | |||
line = text | |||
else | |||
line = bullet .. text | |||
end | |||
if pairPattern ~= "" then | |||
line = apply_pattern(line, pairPattern, pairReplace) | |||
end | |||
table.insert(out, line) | |||
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 baseKey = apply_pattern(tostring(k), keyPattern, "\\1") | |||
local MARK_KEY = "\31KEY\31" | |||
local vRepl = (valueReplace or "\\1"):gsub("\\2", MARK_KEY) | |||
local vStr0 = apply_pattern(vStr, valuePattern, vRepl) | |||
vStr0 = tostring(vStr0):gsub(MARK_KEY, baseKey) | |||
local MARK_VAL = "\31VAL\31" | |||
local kRepl = (keyReplace or "\\1"):gsub("\\2", MARK_VAL) | |||
local keyStr0 = apply_pattern(tostring(k), keyPattern, kRepl) | |||
local keyStr = tostring(keyStr0):gsub(MARK_VAL, vStr0) | |||
vStr = vStr0 | |||
if vStr ~= "" then | |||
local line | |||
if outputType == "enum" then | |||
line = vStr .. " " .. keyStr | |||
else | |||
line = bullet .. keyStr .. sep .. vStr | |||
end | |||
if pairPattern ~= "" then | |||
line = apply_pattern(line, pairPattern, pairReplace) | |||
end | |||
table.insert(out, line) | |||
end | |||
end | |||
end | |||
if outputType == "enum" then | |||
return frame:preprocess(table.concat(out, ", ")) | |||
else | |||
return frame:preprocess(table.concat(out, "\n")) | |||
end | |||
end | end | ||
return p | return p | ||