local addon_name = ... local MAX_DUMPS = 20 local MAX_DEPTH = 24 local function print_msg(msg) print("|cff6fa8dcdev_dump:|r " .. msg) end --- Converts a value into something SavedVariables can store. --- --- SavedVariables only persist strings, numbers, booleans, and tables of --- those, so anything else is replaced with a descriptive placeholder string. local function sanitize(value, depth, seen) local value_type = type(value) if value_type == "string" or value_type == "number" or value_type == "boolean" or value_type == "nil" then return value end if value_type ~= "table" then return "<" .. value_type .. ">" end -- Widgets (frames, textures, and so on) are tables backed by userdata. Dumping -- them recursively would pull in half the UI, so summarize them instead. if type(value.GetObjectType) == "function" and type(rawget(value, 0)) == "userdata" then local ok, object_type = pcall(value.GetObjectType, value) local ok_name, name = pcall(value.GetName, value) return string.format( "<%s %s>", ok and object_type or "widget", (ok_name and name) or "anonymous" ) end if seen[value] then return "" end if depth >= MAX_DEPTH then return "" end seen[value] = true local result = {} for key, inner in pairs(value) do local key_type = type(key) if key_type ~= "string" and key_type ~= "number" and key_type ~= "boolean" then key = "<" .. key_type .. " key> " .. tostring(key) end result[key] = sanitize(inner, depth + 1, seen) end seen[value] = nil return result end local function pack(...) return { n = select("#", ...), ... } end local function dump(expr) local entry = { expr = expr, time = date("%Y-%m-%d %H:%M:%S"), build = { GetBuildInfo() }, } local chunk, compile_error = loadstring("return " .. expr, "=devdump") if not chunk then -- Not an expression, so try it as a statement instead (e.g. `x = 1`). chunk, compile_error = loadstring(expr, "=devdump") end if not chunk then entry.error = compile_error else local results = pack(pcall(chunk)) if results[1] then entry.results = {} entry.result_count = results.n - 1 for i = 2, results.n do entry.results[i - 1] = sanitize(results[i], 0, {}) end else entry.error = results[2] end end local dumps = dev_dump_db.dumps table.insert(dumps, entry) while #dumps > MAX_DUMPS do table.remove(dumps, 1) end if entry.error then print_msg("|cffff6b5eerror:|r " .. tostring(entry.error)) else print_msg(string.format( "saved %d value(s) as dump #%d. /reload to write it to disk.", entry.result_count, #dumps )) end end local frame = CreateFrame("Frame") frame:RegisterEvent("ADDON_LOADED") frame:SetScript("OnEvent", function(self, event, loaded_addon) if loaded_addon ~= addon_name then return end dev_dump_db = dev_dump_db or {} dev_dump_db.dumps = dev_dump_db.dumps or {} self:UnregisterEvent("ADDON_LOADED") end) SLASH_DEV_DUMP1 = "/devdump" SlashCmdList.DEV_DUMP = function(msg) msg = strtrim(msg or "") if msg == "" then print_msg("usage: /devdump | /devdump list | /devdump clear") elseif msg == "clear" then wipe(dev_dump_db.dumps) print_msg("cleared all dumps.") elseif msg == "list" then if #dev_dump_db.dumps == 0 then print_msg("no dumps saved.") end for i, entry in ipairs(dev_dump_db.dumps) do print_msg(string.format("#%d [%s] %s%s", i, entry.time, entry.expr, entry.error and " (error)" or "")) end else dump(msg) end end