|
|
| Строка 1: |
Строка 1: |
| local p = {} | | local p = {} |
|
| |
|
| local cache = {} | | local function parse_timespan(str) |
| local entryCache = {}
| | if not str then return 0 end |
| | str = str:match("^%s*(.-)%s*$") |
|
| |
|
| local function parse_indexed_part(part)
| | local number = tonumber(str) |
| local key, idx = string.match(part, "^(.-)%[(%d+)%]$")
| | if number then return number end |
| 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 #str <= 1 then return 0 end |
| 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 ok, json = pcall(mw.text.jsonEncode, v)
| |
| if ok and json then
| |
| return json
| |
| end
| |
| return ""
| |
| else
| |
| return tostring(v)
| |
| end
| |
| end | |
|
| |
|
| local function to_nowiki(v) | | local value = tonumber(str:sub(1, -2)) |
| local s = tostring(v) | | local unit = str:sub(-1):lower() |
| return "<nowiki>" .. s .. "</nowiki>"
| |
| end
| |
|
| |
|
| local function is_array(tbl)
| | if not value then return 0 end |
| 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)
| | if unit == "s" then |
| local dst = {} | | return value |
| for k, v in pairs(src) do | | elseif unit == "m" then |
| if type(v) == "table" then | | return value * 60 |
| dst[k] = deep_copy(v)
| | elseif unit == "h" then |
| else
| | return value * 3600 |
| dst[k] = v
| | else |
| end | | return 0 |
| end | | end |
| return dst
| |
| end | | end |
|
| |
|
| local function deep_merge(dst, src) | | local function bold(n) |
| for k, v in pairs(src) do | | return "<b>" .. tostring(n) .. "</b>" |
| 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
| |
| | |
| 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 baseUser = "IanComradeBot/"
| |
| local storeName
| |
| if kind == "prototype" then
| |
| storeName = "prototype_store.json"
| |
| else
| |
| storeName = "component_store.json"
| |
| end
| |
| local moduleName = "Module:" .. baseUser .. storeName .. "/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[searchId]
| |
| if type(entry) ~= "table" then
| |
| return ""
| |
| end
| |
| | |
| local value = 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 | | end |
|
| |
|
| function p.flattenField(frame) | | -- Форматирование в "X час. Y мин. Z сек." с жирными числами |
| local args = frame.args or {} | | local function format_time(value) |
| local id = args[1] or ""
| | local total_seconds = math.floor(value) |
| local pagePath = args[2] or ""
| |
| if id == "" or pagePath == "" then return "" end
| |
|
| |
|
| local baseUser = "IanComradeBot/" | | local hours = math.floor(total_seconds / 3600) |
| local moduleName = "Module:" .. baseUser .. pagePath .. "/data" | | local minutes = math.floor((total_seconds % 3600) / 60) |
| | | local seconds = total_seconds % 60 |
| 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 = resolve_entry(data, id) or {} | |
| | |
| if type(entry) ~= "table" then return "" end
| |
|
| |
|
| local parts = {} | | 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 next(v) == nil then
| |
| elseif is_array(v) then
| |
| local ok, json = pcall(mw.text.jsonEncode, v)
| |
| if ok and json then
| |
| parts[#parts + 1] = key .. "=" .. tostring(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 .. "=" .. tostring(v)
| |
| end
| |
| end
| |
| end
| |
|
| |
|
| walk(entry, "") | | if hours > 0 then |
| | | table.insert(parts, bold(hours) .. " час.") |
| 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 | | end |
| | | if minutes > 0 then |
| local entryKey = moduleName .. "|" .. (id ~= "" and id or "default")
| | table.insert(parts, bold(minutes) .. " мин.") |
| local entry = entryCache[entryKey]
| |
| if not entry then | |
| entry = resolve_entry(data, id) | |
| entryCache[entryKey] = entry
| |
| end | | end |
| if entry == nil then return "" end | | if seconds > 0 or #parts == 0 then |
| | | table.insert(parts, bold(seconds) .. " сек.") |
| if keyPath == "" then
| |
| return format_value(entry)
| |
| end | | end |
|
| |
|
| local value = get_by_path(entry, keyPath) | | return table.concat(parts, " ") |
| return format_value(value)
| |
| end | | end |
|
| |
|
| function p.getId(frame) | | function p.main(frame) |
| local args = frame.args or {} | | local args = frame.args[1] |
| local searchValue = args[1] or ""
| | local time = parse_timespan(args) |
| local pagePath = args[2] or ""
| | return format_time(time) |
| local keyPath = args[3] or "" | |
| | |
| if searchValue == "" or pagePath == "" or keyPath == "" 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 idsTable = data.id
| |
| if type(idsTable) ~= "table" then
| |
| return ""
| |
| end
| |
| | |
| local target = tostring(searchValue)
| |
| | |
| for idKey, entry in pairs(idsTable) do
| |
| if type(entry) == "table" then
| |
| local v = get_by_path(entry, keyPath)
| |
| if v ~= nil then
| |
| if type(v) == "table" then
| |
| if is_array(v) then
| |
| for _, item in ipairs(v) do
| |
| if tostring(item) == target then
| |
| return idKey
| |
| end
| |
| end
| |
| else
| |
| for _, item in pairs(v) do
| |
| if tostring(item) == target then
| |
| return idKey
| |
| end
| |
| end
| |
| end
| |
| else
| |
| if tostring(v) == target then
| |
| return idKey
| |
| end
| |
| end
| |
| end
| |
| end
| |
| 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 = args[4] or ""
| |
| | |
| if searchValue == "" or pagePath == "" or keyPath == "" or tplPath == "" 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 idsTable = data.id | |
| if type(idsTable) ~= "table" then
| |
| return ""
| |
| end
| |
| | |
| 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 then
| |
| if type(v) == "table" then
| |
| if is_array(v) then
| |
| for _, item in ipairs(v) do
| |
| if tostring(item) == target then
| |
| matches[#matches + 1] = idKey
| |
| break
| |
| end
| |
| end
| |
| else
| |
| for _, item in pairs(v) do
| |
| if tostring(item) == target then
| |
| matches[#matches + 1] = idKey
| |
| break
| |
| end
| |
| end
| |
| end
| |
| else
| |
| if tostring(v) == target then
| |
| matches[#matches + 1] = idKey
| |
| end
| |
| end
| |
| end
| |
| end
| |
| end
| |
| | |
| 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, "\n")
| |
| if type(frame.preprocess) == "function" then
| |
| return frame:preprocess(result)
| |
| end
| |
| | |
| return 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 .. "}}"
| |
| | |
| if type(frame.preprocess) == "function" then
| |
| return frame:preprocess(tplStr)
| |
| end
| |
| | |
| return 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, "\n")
| |
| if type(frame.preprocess) == "function" then
| |
| return frame:preprocess(result)
| |
| end
| |
| | |
| return result
| |
| end | | end |
|
| |
|
| return p | | return p |