Модуль:GetField: различия между версиями
Материал из Space Station 14 Вики
Pok (обсуждение | вклад) мНет описания правки |
Pok (обсуждение | вклад) мНет описания правки |
||
| (не показано 13 промежуточных версий этого же участника) | |||
| Строка 1: | Строка 1: | ||
local p = {} | local p = {} | ||
local | local cache = {} | ||
local entryCache = {} | |||
local function parse_indexed_part(part) | 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 | end | ||
local function get_by_path(tbl, path) | 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) | |||
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 max = 0 | |||
local hasNonNumber = false | |||
for k in pairs(v) do | |||
if type(k) ~= "number" then | |||
hasNonNumber = true | |||
break | |||
else | |||
if k > max then max = k end | |||
end | |||
end | |||
if not hasNonNumber and max > 0 then | |||
local out = {} | |||
for i = 1, max do | |||
out[#out + 1] = format_value(v[i]) | |||
end | |||
return table.concat(out, ", ") | |||
else | |||
local out = {} | |||
for k, val in pairs(v) do | |||
out[#out + 1] = tostring(k) .. ": " .. format_value(val) | |||
end | |||
return table.concat(out, ", ") | |||
end | |||
else | |||
return tostring(v) | |||
end | |||
end | |||
local function to_nowiki(v) | |||
local s = tostring(v) | |||
return "<nowiki>" .. s .. "</nowiki>" | |||
end | end | ||
local function is_array( | 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 | |||
function p.findInProto(frame) | |||
local args = frame.args or {} | |||
local protoKey = args[1] or "" | |||
local fieldId = args[2] or "" | |||
if protoKey == "" or fieldId == "" then | |||
return "[]" | |||
end | |||
local moduleName = "Module:IanComradeBot/prototype.json/data" | |||
local data = cache[moduleName] | |||
if not data then | |||
local ok, loaded = pcall(mw.loadData, moduleName) | |||
if not ok or not loaded then | |||
return "[]" | |||
end | |||
data = loaded | |||
cache[moduleName] = data | |||
end | |||
local proto = data[protoKey] | |||
if type(proto) ~= "table" then | |||
return "[]" | |||
end | |||
local value = proto[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 | 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 baseUser = "IanComradeBot/" | |||
local moduleName = "Module:" .. baseUser .. pagePath .. "/data" | |||
local data = cache[moduleName] | |||
if not data then | |||
local ok, loaded = pcall(mw.loadData, moduleName) | |||
if not ok or not loaded then return "" end | |||
data = loaded | |||
cache[moduleName] = data | |||
end | |||
local entry = data[id] | |||
if entry == nil then | |||
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) | |||
entry = merged | |||
else | |||
entry = deep_copy(specific) | |||
end | |||
end | |||
end | |||
end | |||
if entry == nil then | |||
local base = data["default"] | |||
if type(base) == "table" then | |||
entry = deep_copy(base) | |||
else | |||
entry = {} | |||
end | |||
end | |||
if type(entry) ~= "table" then return "" end | |||
local parts = {} | |||
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 key = (prefix == "" and tostring(k) or prefix .. "." .. tostring(k)) | |||
if type(v) == "table" then | |||
if is_array(v) then | |||
local ok, json = pcall(mw.text.jsonEncode, v) | |||
if ok and json then | |||
parts[#parts + 1] = key .. "=" .. to_nowiki(json) | |||
end | |||
else | |||
local ok, json = pcall(mw.text.jsonEncode, v) | |||
if ok and json then | |||
parts[#parts + 1] = key .. "=" .. to_nowiki(json) | |||
end | |||
walk(v, key) | |||
end | |||
else | |||
parts[#parts + 1] = key .. "=" .. to_nowiki(v) | |||
end | |||
end | |||
end | |||
walk(entry, "") | |||
return table.concat(parts, "|") | |||
end | end | ||
function p.get(frame) | 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 baseUser = "IanComradeBot/" | |||
local moduleName = "Module:" .. baseUser .. pagePath .. "/data" | |||
local data = cache[moduleName] | |||
if not data then | |||
local ok, loaded = pcall(mw.loadData, moduleName) | |||
if not ok or not loaded then return "" end | |||
data = loaded | |||
cache[moduleName] = data | |||
end | |||
local entryKey = moduleName .. "|" .. (id ~= "" and id or "default") | |||
local entry = entryCache[entryKey] | |||
if not entry then | |||
if id ~= "" then entry = data[id] end | |||
if entry == nil then entry = data["default"] end | |||
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 | end | ||
function p. | 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 .. "}}" | |||
if type(frame.preprocess) == "function" then | |||
return frame:preprocess(tplStr) | |||
end | |||
return tplStr | |||
end | |||
function p.getTplProto(frame) | |||
local args = frame.args or {} | |||
local searchId = args[1] or "" | |||
local protoId = args[2] or "" | |||
local tplPath = args[3] or "" | |||
local pagePath = "prototype/" .. protoId .. ".json" | |||
if searchId == "" or protoId == "" or tplPath == "" then | |||
return "" | |||
end | |||
local idsJson = p.findInProto({ args = { searchId, protoId } }) | |||
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, "\n") | |||
if type(frame.preprocess) == "function" then | |||
return frame:preprocess(result) | |||
end | |||
return result | |||
end | end | ||
return p | return p | ||
Текущая версия от 21:14, 25 февраля 2026
Для документации этого модуля может быть создана страница Модуль:GetField/doc
local p = {}
local cache = {}
local entryCache = {}
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)
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 max = 0
local hasNonNumber = false
for k in pairs(v) do
if type(k) ~= "number" then
hasNonNumber = true
break
else
if k > max then max = k end
end
end
if not hasNonNumber and max > 0 then
local out = {}
for i = 1, max do
out[#out + 1] = format_value(v[i])
end
return table.concat(out, ", ")
else
local out = {}
for k, val in pairs(v) do
out[#out + 1] = tostring(k) .. ": " .. format_value(val)
end
return table.concat(out, ", ")
end
else
return tostring(v)
end
end
local function to_nowiki(v)
local s = tostring(v)
return "<nowiki>" .. s .. "</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
function p.findInProto(frame)
local args = frame.args or {}
local protoKey = args[1] or ""
local fieldId = args[2] or ""
if protoKey == "" or fieldId == "" then
return "[]"
end
local moduleName = "Module:IanComradeBot/prototype.json/data"
local data = cache[moduleName]
if not data then
local ok, loaded = pcall(mw.loadData, moduleName)
if not ok or not loaded then
return "[]"
end
data = loaded
cache[moduleName] = data
end
local proto = data[protoKey]
if type(proto) ~= "table" then
return "[]"
end
local value = proto[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 baseUser = "IanComradeBot/"
local moduleName = "Module:" .. baseUser .. pagePath .. "/data"
local data = cache[moduleName]
if not data then
local ok, loaded = pcall(mw.loadData, moduleName)
if not ok or not loaded then return "" end
data = loaded
cache[moduleName] = data
end
local entry = data[id]
if entry == nil then
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)
entry = merged
else
entry = deep_copy(specific)
end
end
end
end
if entry == nil then
local base = data["default"]
if type(base) == "table" then
entry = deep_copy(base)
else
entry = {}
end
end
if type(entry) ~= "table" then return "" end
local parts = {}
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 key = (prefix == "" and tostring(k) or prefix .. "." .. tostring(k))
if type(v) == "table" then
if is_array(v) then
local ok, json = pcall(mw.text.jsonEncode, v)
if ok and json then
parts[#parts + 1] = key .. "=" .. to_nowiki(json)
end
else
local ok, json = pcall(mw.text.jsonEncode, v)
if ok and json then
parts[#parts + 1] = key .. "=" .. to_nowiki(json)
end
walk(v, key)
end
else
parts[#parts + 1] = key .. "=" .. to_nowiki(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 baseUser = "IanComradeBot/"
local moduleName = "Module:" .. baseUser .. pagePath .. "/data"
local data = cache[moduleName]
if not data then
local ok, loaded = pcall(mw.loadData, moduleName)
if not ok or not loaded then return "" end
data = loaded
cache[moduleName] = data
end
local entryKey = moduleName .. "|" .. (id ~= "" and id or "default")
local entry = entryCache[entryKey]
if not entry then
if id ~= "" then entry = data[id] end
if entry == nil then entry = data["default"] end
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.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 .. "}}"
if type(frame.preprocess) == "function" then
return frame:preprocess(tplStr)
end
return tplStr
end
function p.getTplProto(frame)
local args = frame.args or {}
local searchId = args[1] or ""
local protoId = args[2] or ""
local tplPath = args[3] or ""
local pagePath = "prototype/" .. protoId .. ".json"
if searchId == "" or protoId == "" or tplPath == "" then
return ""
end
local idsJson = p.findInProto({ args = { searchId, protoId } })
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, "\n")
if type(frame.preprocess) == "function" then
return frame:preprocess(result)
end
return result
end
return p