Модуль:GetField: различия между версиями
Материал из Space Station 14 Вики
Pok (обсуждение | вклад) мНет описания правки |
Pok (обсуждение | вклад) мНет описания правки |
||
| (не показаны 23 промежуточные версии этого же участника) | |||
| Строка 3: | Строка 3: | ||
local cache = {} | local cache = {} | ||
local entryCache = {} | local entryCache = {} | ||
local BASE_USER = "IanComradeBot/" | |||
local function get_module_name(pagePath) | |||
return "Module:" .. BASE_USER .. pagePath .. "/data" | |||
end | |||
local function load_cached_data(moduleName) | |||
local data = cache[moduleName] | |||
if data then | |||
return data | |||
end | |||
local ok, loaded = pcall(mw.loadData, moduleName) | |||
if not ok or not loaded then | |||
return nil | |||
end | |||
cache[moduleName] = loaded | |||
return loaded | |||
end | |||
local function parse_indexed_part(part) | local function parse_indexed_part(part) | ||
| Строка 39: | Строка 59: | ||
local function format_value(v) | local function format_value(v) | ||
local okJson, json = pcall(mw.text.jsonEncode, v) | |||
if okJson and json == "null" then | |||
return "null" | |||
end | |||
if v == nil then return "" end | if v == nil then return "" end | ||
local t = type(v) | local t = type(v) | ||
if t == "string" or t == "number" or t == "boolean" then | if t == "string" or t == "number" or t == "boolean" then | ||
return tostring(v) | return tostring(v) | ||
elseif t == "table" then | elseif t == "table" then | ||
local | local ok, json2 = pcall(mw.text.jsonEncode, v) | ||
if ok and json2 then | |||
return json2 | |||
if | |||
return | |||
end | end | ||
return "" | |||
else | else | ||
return tostring(v) | return tostring(v) | ||
| Строка 73: | Строка 81: | ||
local function to_nowiki(v) | local function to_nowiki(v) | ||
return "<nowiki>" .. v .. "</nowiki>" | |||
return "<nowiki>" .. | |||
end | end | ||
| Строка 114: | Строка 121: | ||
end | end | ||
function p. | local function resolve_entry(data, id) | ||
if type(data) ~= "table" then | |||
return nil | |||
end | |||
if id and id ~= "" then | |||
local direct = data[id] | |||
if direct ~= nil then | |||
return direct | |||
end | |||
local idsTable = data.id | |||
if type(idsTable) == "table" then | |||
local specific = idsTable[id] | |||
if type(specific) == "table" then | |||
local base = data["default"] | |||
if type(base) == "table" then | |||
local merged = deep_copy(base) | |||
deep_merge(merged, specific) | |||
return merged | |||
end | |||
return deep_copy(specific) | |||
end | |||
end | |||
end | |||
local base = data["default"] | |||
if type(base) == "table" then | |||
return deep_copy(base) | |||
end | |||
return nil | |||
end | |||
local function contains_target(v, target) | |||
if type(v) == "table" then | |||
if is_array(v) then | |||
for _, item in ipairs(v) do | |||
if tostring(item) == target then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
for _, item in pairs(v) do | |||
if tostring(item) == target then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
return tostring(v) == target | |||
end | |||
local function find_matching_ids(idsTable, keyPath, searchValue) | |||
local target = tostring(searchValue) | |||
local matches = {} | |||
for idKey, entry in pairs(idsTable) do | |||
if type(entry) == "table" then | |||
local v = get_by_path(entry, keyPath) | |||
if v ~= nil and contains_target(v, target) then | |||
matches[#matches + 1] = idKey | |||
end | |||
end | |||
end | |||
return matches | |||
end | |||
local function preprocess_or_return(frame, text) | |||
if type(frame.preprocess) == "function" then | |||
return frame:preprocess(text) | |||
end | |||
return text | |||
end | |||
local function get_field_loose(entry, fieldId) | |||
local value = entry[fieldId] | |||
if value ~= nil then return value end | |||
if fieldId == "" then return nil end | |||
local first = string.sub(fieldId, 1, 1) | |||
local tail = string.sub(fieldId, 2) | |||
value = entry[string.lower(first) .. tail] | |||
if value ~= nil then return value end | |||
return entry[string.upper(first) .. tail] | |||
end | |||
function p.findInGenerator(frame) | |||
local args = frame.args or {} | local args = frame.args or {} | ||
local | local searchId = args[1] or "" | ||
local fieldId = args[ | local kind = (args[2] or ""):lower() | ||
local fieldId = args[3] or "" | |||
if | if searchId == "" or fieldId == "" then | ||
return " | return "" | ||
end | |||
if kind ~= "prototype" and kind ~= "component" then | |||
return "" | |||
end | end | ||
local | local storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json" | ||
local moduleName = get_module_name(storeName) | |||
local data = | local data = load_cached_data(moduleName) | ||
if not data then | if not data then | ||
return "" | |||
end | end | ||
local | local entry = data[searchId] | ||
if type( | if type(entry) ~= "table" then | ||
return " | return "" | ||
end | end | ||
local value = | local value = get_field_loose(entry, fieldId) | ||
if value == nil then | if value == nil then | ||
return " | return "" | ||
end | end | ||
| Строка 164: | Строка 261: | ||
if id == "" or pagePath == "" then return "" end | if id == "" or pagePath == "" then return "" end | ||
local | local moduleName = get_module_name(pagePath) | ||
local moduleName | local data = load_cached_data(moduleName) | ||
if not data then return "" end | |||
local | local entry = resolve_entry(data, id) or {} | ||
if type(entry) ~= "table" then return "" end | |||
local parts = {} | |||
local | local function append_table_json(key, value) | ||
if | local ok, json = pcall(mw.text.jsonEncode, value) | ||
if ok and json then | |||
parts[#parts + 1] = key .. "=" .. to_nowiki(json) | |||
end | end | ||
end | end | ||
local function walk(tbl, prefix) | local function walk(tbl, prefix) | ||
local keys = {} | local keys = {} | ||
| Строка 212: | Строка 283: | ||
for _, k in ipairs(keys) do | for _, k in ipairs(keys) do | ||
local v = tbl[k] | local v = tbl[k] | ||
local key = (prefix == "" and | local kStr = tostring(k) | ||
local key = (prefix == "" and kStr or prefix .. "." .. kStr) | |||
if type(v) == "table" then | if type(v) == "table" then | ||
if | if next(v) == nil then | ||
else | else | ||
append_table_json(key, v) | |||
if | if not is_array(v) then | ||
walk(v, key) | |||
end | end | ||
end | end | ||
else | else | ||
parts[#parts + 1] = key .. "=" .. | parts[#parts + 1] = key .. "=" .. tostring(v) | ||
end | end | ||
end | end | ||
| Строка 245: | Строка 312: | ||
if pagePath == "" then return "" end | if pagePath == "" then return "" end | ||
local moduleName = get_module_name(pagePath) | |||
local moduleName = | local data = load_cached_data(moduleName) | ||
if not data then return "" end | |||
local data = | |||
if not data | |||
local entryKey = moduleName .. "|" .. (id ~= "" and id or "default") | local entryKey = moduleName .. "|" .. (id ~= "" and id or "default") | ||
local entry = entryCache[entryKey] | local entry = entryCache[entryKey] | ||
if not entry then | if not entry then | ||
entry = resolve_entry(data, id) | |||
entryCache[entryKey] = entry | entryCache[entryKey] = entry | ||
end | end | ||
| Строка 271: | Строка 330: | ||
local value = get_by_path(entry, keyPath) | local value = get_by_path(entry, keyPath) | ||
return format_value(value) | return format_value(value) | ||
end | |||
function p.getId(frame) | |||
local args = frame.args or {} | |||
local searchValue = args[1] or "" | |||
local pagePath = args[2] or "" | |||
local keyPath = args[3] or "" | |||
if searchValue == "" or pagePath == "" or keyPath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then return "[]" end | |||
local idsTable = data.id | |||
if type(idsTable) ~= "table" then | |||
return "" | |||
end | |||
local matches = find_matching_ids(idsTable, keyPath, searchValue) | |||
if #matches == 0 then | |||
return "" | |||
end | |||
local ok, json = pcall(mw.text.jsonEncode, matches) | |||
if ok and json then | |||
return json | |||
end | |||
return "" | |||
end | |||
function p.getTplId(frame) | |||
local args = frame.args or {} | |||
local searchValue = args[1] or "" | |||
local pagePath = args[2] or "" | |||
local keyPath = args[3] or "" | |||
local tplPath = mw.text.unstripNoWiki(args[4] or "") | |||
if searchValue == "" or pagePath == "" or keyPath == "" or tplPath == "" then | |||
return "" | |||
end | |||
local moduleName = get_module_name(pagePath) | |||
local data = load_cached_data(moduleName) | |||
if not data then return "" end | |||
local idsTable = data.id | |||
if type(idsTable) ~= "table" then | |||
return "" | |||
end | |||
local matches = find_matching_ids(idsTable, keyPath, searchValue) | |||
if #matches == 0 then | |||
return "" | |||
end | |||
local out = {} | |||
for _, idKey in ipairs(matches) do | |||
local tpl = p.getTpl({ args = { idKey, pagePath, tplPath } }) | |||
if tpl ~= "" then | |||
out[#out + 1] = tpl | |||
end | |||
end | |||
if #out == 0 then | |||
return "" | |||
end | |||
local result = table.concat(out, " ") | |||
return preprocess_or_return(frame, result) | |||
end | end | ||
| Строка 290: | Строка 424: | ||
tplStr = tplStr .. "}}" | tplStr = tplStr .. "}}" | ||
return preprocess_or_return(frame, tplStr) | |||
end | end | ||
function p. | function p.getTplGenerator(frame) | ||
local args = frame.args or {} | local args = frame.args or {} | ||
local searchId = args[1] or "" | local searchId = args[1] or "" | ||
local | local kind = (args[2] or ""):lower() | ||
local | local generatorId = args[3] or "" | ||
local | local tplPath = args[4] or "" | ||
if searchId == "" or | if searchId == "" or generatorId == "" or tplPath == "" then | ||
return "" | |||
end | |||
if kind ~= "prototype" and kind ~= "component" then | |||
return "" | return "" | ||
end | end | ||
local idsJson = p. | local dir = (kind == "prototype") and "prototype/" or "component/" | ||
local pagePath = dir .. generatorId .. ".json" | |||
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } }) | |||
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "") | local ok, ids = pcall(mw.text.jsonDecode, idsJson or "") | ||
if not ok or type(ids) ~= "table" or #ids == 0 then | if not ok or type(ids) ~= "table" or #ids == 0 then | ||
| Строка 322: | Строка 458: | ||
end | end | ||
local result = table.concat(out, " | local result = table.concat(out, " ") | ||
if type(frame. | return preprocess_or_return(frame, result) | ||
return | end | ||
function p.getGenerator(frame) | |||
local args = frame.args or {} | |||
local searchId = args[1] or "" | |||
local kind = (args[2] or ""):lower() | |||
local generatorId = args[3] or "" | |||
if searchId == "" or generatorId == "" then | |||
return "" | |||
end | |||
if kind ~= "prototype" and kind ~= "component" then | |||
return "" | |||
end | |||
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } }) | |||
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "") | |||
if not ok or type(ids) ~= "table" or #ids == 0 then | |||
return "" | |||
end | |||
local okOut, outJson = pcall(mw.text.jsonEncode, ids) | |||
if okOut and outJson then | |||
return outJson | |||
end | |||
return "" | |||
end | |||
function p.hasComp(frame) | |||
local args = frame.args or {} | |||
local entityId = args[1] or "" | |||
local compName = args[2] or "" | |||
if entityId == "" or compName == "" then | |||
return "false" | |||
end | |||
local moduleName = get_module_name("component.json") | |||
local data = load_cached_data(moduleName) | |||
if not data then | |||
return "false" | |||
end | |||
if type(data) ~= "table" then | |||
return "false" | |||
end | |||
local entry = data[entityId] | |||
if type(entry) ~= "table" then | |||
return "false" | |||
end | |||
local target = tostring(compName) | |||
for _, v in ipairs(entry) do | |||
if tostring(v) == target then | |||
return "true" | |||
end | |||
end | end | ||
return | return "false" | ||
end | end | ||
return p | return p | ||
Текущая версия от 00:06, 9 марта 2026
Для документации этого модуля может быть создана страница Модуль:GetField/doc
local p = {}
local cache = {}
local entryCache = {}
local BASE_USER = "IanComradeBot/"
local function get_module_name(pagePath)
return "Module:" .. BASE_USER .. pagePath .. "/data"
end
local function load_cached_data(moduleName)
local data = cache[moduleName]
if data then
return data
end
local ok, loaded = pcall(mw.loadData, moduleName)
if not ok or not loaded then
return nil
end
cache[moduleName] = loaded
return loaded
end
local function parse_indexed_part(part)
local key, idx = string.match(part, "^(.-)%[(%d+)%]$")
if key then
return key, tonumber(idx)
end
local num = tonumber(part)
if num then
return nil, num
end
return part, nil
end
local function get_by_path(tbl, path)
if not tbl or path == "" then return nil end
local cur = tbl
for part in string.gmatch(path, "([^%.]+)") do
local key, idx = parse_indexed_part(part)
if key and key ~= "" then
if type(cur) ~= "table" then return nil end
local nextCur = cur[key]
if nextCur == nil then
nextCur = cur["!type:" .. key]
end
cur = nextCur
end
if idx then
if type(cur) ~= "table" then return nil end
cur = cur[idx]
end
if cur == nil then return nil end
end
return cur
end
local function format_value(v)
local okJson, json = pcall(mw.text.jsonEncode, v)
if okJson and json == "null" then
return "null"
end
if v == nil then return "" end
local t = type(v)
if t == "string" or t == "number" or t == "boolean" then
return tostring(v)
elseif t == "table" then
local ok, json2 = pcall(mw.text.jsonEncode, v)
if ok and json2 then
return json2
end
return ""
else
return tostring(v)
end
end
local function to_nowiki(v)
return "<nowiki>" .. v .. "</nowiki>"
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 deep_copy(src)
local dst = {}
for k, v in pairs(src) do
if type(v) == "table" then
dst[k] = deep_copy(v)
else
dst[k] = v
end
end
return dst
end
local function deep_merge(dst, src)
for k, v in pairs(src) do
if type(v) == "table" and type(dst[k]) == "table" then
deep_merge(dst[k], v)
elseif type(v) == "table" then
dst[k] = deep_copy(v)
else
dst[k] = v
end
end
end
local function resolve_entry(data, id)
if type(data) ~= "table" then
return nil
end
if id and id ~= "" then
local direct = data[id]
if direct ~= nil then
return direct
end
local idsTable = data.id
if type(idsTable) == "table" then
local specific = idsTable[id]
if type(specific) == "table" then
local base = data["default"]
if type(base) == "table" then
local merged = deep_copy(base)
deep_merge(merged, specific)
return merged
end
return deep_copy(specific)
end
end
end
local base = data["default"]
if type(base) == "table" then
return deep_copy(base)
end
return nil
end
local function contains_target(v, target)
if type(v) == "table" then
if is_array(v) then
for _, item in ipairs(v) do
if tostring(item) == target then
return true
end
end
return false
end
for _, item in pairs(v) do
if tostring(item) == target then
return true
end
end
return false
end
return tostring(v) == target
end
local function find_matching_ids(idsTable, keyPath, searchValue)
local target = tostring(searchValue)
local matches = {}
for idKey, entry in pairs(idsTable) do
if type(entry) == "table" then
local v = get_by_path(entry, keyPath)
if v ~= nil and contains_target(v, target) then
matches[#matches + 1] = idKey
end
end
end
return matches
end
local function preprocess_or_return(frame, text)
if type(frame.preprocess) == "function" then
return frame:preprocess(text)
end
return text
end
local function get_field_loose(entry, fieldId)
local value = entry[fieldId]
if value ~= nil then return value end
if fieldId == "" then return nil end
local first = string.sub(fieldId, 1, 1)
local tail = string.sub(fieldId, 2)
value = entry[string.lower(first) .. tail]
if value ~= nil then return value end
return entry[string.upper(first) .. tail]
end
function p.findInGenerator(frame)
local args = frame.args or {}
local searchId = args[1] or ""
local kind = (args[2] or ""):lower()
local fieldId = args[3] or ""
if searchId == "" or fieldId == "" then
return ""
end
if kind ~= "prototype" and kind ~= "component" then
return ""
end
local storeName = (kind == "prototype") and "prototype_store.json" or "component_store.json"
local moduleName = get_module_name(storeName)
local data = load_cached_data(moduleName)
if not data then
return ""
end
local entry = data[searchId]
if type(entry) ~= "table" then
return ""
end
local value = get_field_loose(entry, fieldId)
if value == nil then
return ""
end
local out = {}
local t = type(value)
if t == "table" then
for _, v in ipairs(value) do
out[#out + 1] = v
end
else
out[1] = value
end
return mw.text.jsonEncode(out)
end
function p.flattenField(frame)
local args = frame.args or {}
local id = args[1] or ""
local pagePath = args[2] or ""
if id == "" or pagePath == "" then return "" end
local moduleName = get_module_name(pagePath)
local data = load_cached_data(moduleName)
if not data then return "" end
local entry = resolve_entry(data, id) or {}
if type(entry) ~= "table" then return "" end
local parts = {}
local function append_table_json(key, value)
local ok, json = pcall(mw.text.jsonEncode, value)
if ok and json then
parts[#parts + 1] = key .. "=" .. to_nowiki(json)
end
end
local function walk(tbl, prefix)
local keys = {}
for k in pairs(tbl) 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 = tbl[k]
local kStr = tostring(k)
local key = (prefix == "" and kStr or prefix .. "." .. kStr)
if type(v) == "table" then
if next(v) == nil then
else
append_table_json(key, v)
if not is_array(v) then
walk(v, key)
end
end
else
parts[#parts + 1] = key .. "=" .. tostring(v)
end
end
end
walk(entry, "")
return table.concat(parts, "|")
end
function p.get(frame)
local args = frame.args or {}
local id = args[1] or ""
local pagePath = args[2] or ""
local keyPath = args[3] or ""
if pagePath == "" then return "" end
local moduleName = get_module_name(pagePath)
local data = load_cached_data(moduleName)
if not data then return "" end
local entryKey = moduleName .. "|" .. (id ~= "" and id or "default")
local entry = entryCache[entryKey]
if not entry then
entry = resolve_entry(data, id)
entryCache[entryKey] = entry
end
if entry == nil then return "" end
if keyPath == "" then
return format_value(entry)
end
local value = get_by_path(entry, keyPath)
return format_value(value)
end
function p.getId(frame)
local args = frame.args or {}
local searchValue = args[1] or ""
local pagePath = args[2] or ""
local keyPath = args[3] or ""
if searchValue == "" or pagePath == "" or keyPath == "" then
return ""
end
local moduleName = get_module_name(pagePath)
local data = load_cached_data(moduleName)
if not data then return "[]" end
local idsTable = data.id
if type(idsTable) ~= "table" then
return ""
end
local matches = find_matching_ids(idsTable, keyPath, searchValue)
if #matches == 0 then
return ""
end
local ok, json = pcall(mw.text.jsonEncode, matches)
if ok and json then
return json
end
return ""
end
function p.getTplId(frame)
local args = frame.args or {}
local searchValue = args[1] or ""
local pagePath = args[2] or ""
local keyPath = args[3] or ""
local tplPath = mw.text.unstripNoWiki(args[4] or "")
if searchValue == "" or pagePath == "" or keyPath == "" or tplPath == "" then
return ""
end
local moduleName = get_module_name(pagePath)
local data = load_cached_data(moduleName)
if not data then return "" end
local idsTable = data.id
if type(idsTable) ~= "table" then
return ""
end
local matches = find_matching_ids(idsTable, keyPath, searchValue)
if #matches == 0 then
return ""
end
local out = {}
for _, idKey in ipairs(matches) do
local tpl = p.getTpl({ args = { idKey, pagePath, tplPath } })
if tpl ~= "" then
out[#out + 1] = tpl
end
end
if #out == 0 then
return ""
end
local result = table.concat(out, " ")
return preprocess_or_return(frame, result)
end
function p.getTpl(frame)
local args = frame.args or {}
local id = args[1] or ""
local pagePath = args[2] or ""
local tplPath = args[3] or ""
if id == "" or pagePath == "" or tplPath == "" then
return ""
end
local extra = p.flattenField({ args = { id, pagePath } }) or ""
local tplStr = "{{" .. tostring(tplPath) .. "|id=" .. tostring(id)
if extra ~= "" then
tplStr = tplStr .. "|" .. extra
end
tplStr = tplStr .. "}}"
return preprocess_or_return(frame, tplStr)
end
function p.getTplGenerator(frame)
local args = frame.args or {}
local searchId = args[1] or ""
local kind = (args[2] or ""):lower()
local generatorId = args[3] or ""
local tplPath = args[4] or ""
if searchId == "" or generatorId == "" or tplPath == "" then
return ""
end
if kind ~= "prototype" and kind ~= "component" then
return ""
end
local dir = (kind == "prototype") and "prototype/" or "component/"
local pagePath = dir .. generatorId .. ".json"
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } })
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
if not ok or type(ids) ~= "table" or #ids == 0 then
return ""
end
local out = {}
for _, id in ipairs(ids) do
local tpl = p.getTpl({ args = { id, pagePath, tplPath } })
if tpl ~= "" then
out[#out + 1] = tpl
end
end
local result = table.concat(out, " ")
return preprocess_or_return(frame, result)
end
function p.getGenerator(frame)
local args = frame.args or {}
local searchId = args[1] or ""
local kind = (args[2] or ""):lower()
local generatorId = args[3] or ""
if searchId == "" or generatorId == "" then
return ""
end
if kind ~= "prototype" and kind ~= "component" then
return ""
end
local idsJson = p.findInGenerator({ args = { searchId, kind, generatorId } })
local ok, ids = pcall(mw.text.jsonDecode, idsJson or "")
if not ok or type(ids) ~= "table" or #ids == 0 then
return ""
end
local okOut, outJson = pcall(mw.text.jsonEncode, ids)
if okOut and outJson then
return outJson
end
return ""
end
function p.hasComp(frame)
local args = frame.args or {}
local entityId = args[1] or ""
local compName = args[2] or ""
if entityId == "" or compName == "" then
return "false"
end
local moduleName = get_module_name("component.json")
local data = load_cached_data(moduleName)
if not data then
return "false"
end
if type(data) ~= "table" then
return "false"
end
local entry = data[entityId]
if type(entry) ~= "table" then
return "false"
end
local target = tostring(compName)
for _, v in ipairs(entry) do
if tostring(v) == target then
return "true"
end
end
return "false"
end
return p