409 lines
13 KiB
Lua
409 lines
13 KiB
Lua
------------------------------------------------------------------------------
|
|
-- Dump.lua
|
|
--
|
|
-- Contributed by Iriel, Esamynn and Kirov from DevTools v1.11
|
|
-- /dump Implementation
|
|
--
|
|
-- Globals: DevTools, SLASH_DEVTOOLSDUMP1, DevTools_Dump, DevTools_RunDump
|
|
-- Globals: DEVTOOLS_MAX_ENTRY_CUTOFF, DEVTOOLS_LONG_STRING_CUTOFF
|
|
-- Globals: DEVTOOLS_DEPTH_CUTOFF, DEVTOOLS_INDENT
|
|
-- Globals: DEVTOOLS_USE_TABLE_CACHE, DEVTOOLS_USE_FUNCTION_CACHE
|
|
-- Globals: DEVTOOLS_USE_USERDATA_CACHE
|
|
---------------------------------------------------------------------------
|
|
local DT = {};
|
|
|
|
DEVTOOLS_MAX_ENTRY_CUTOFF = 30; -- Maximum table entries shown
|
|
DEVTOOLS_LONG_STRING_CUTOFF = 200; -- Maximum string size shown
|
|
DEVTOOLS_DEPTH_CUTOFF = 10; -- Maximum table depth
|
|
DEVTOOLS_USE_TABLE_CACHE = true; -- Look up table names
|
|
DEVTOOLS_USE_FUNCTION_CACHE = true;-- Look up function names
|
|
DEVTOOLS_USE_USERDATA_CACHE = true;-- Look up userdata names
|
|
DEVTOOLS_INDENT=' '; -- Indentation string
|
|
|
|
local DEVTOOLS_TYPE_COLOR="|cff88ff88";
|
|
local DEVTOOLS_TABLEREF_COLOR="|cffffcc00";
|
|
local DEVTOOLS_CUTOFF_COLOR="|cffff0000";
|
|
local DEVTOOLS_TABLEKEY_COLOR="|cff88ccff";
|
|
|
|
local FORMATS = {};
|
|
-- prefix type suffix
|
|
FORMATS["opaqueTypeVal"] = "%s" .. DEVTOOLS_TYPE_COLOR .. "<%s>|r%s";
|
|
-- prefix type name suffix
|
|
FORMATS["opaqueTypeValName"] = "%s" .. DEVTOOLS_TYPE_COLOR .. "<%s %s>|r%s";
|
|
-- type
|
|
FORMATS["opaqueTypeKey"] = "<%s>";
|
|
-- type name
|
|
FORMATS["opaqueTypeKeyName"] = "<%s %s>";
|
|
-- value
|
|
FORMATS["bracketTableKey"] = "[%s]";
|
|
-- prefix value
|
|
FORMATS["tableKeyAssignPrefix"] = DEVTOOLS_TABLEKEY_COLOR .. "%s%s|r=";
|
|
-- prefix cutoff
|
|
FORMATS["tableEntriesSkipped"] = "%s" .. DEVTOOLS_CUTOFF_COLOR .. "<skipped %s>|r";
|
|
-- prefix suffix
|
|
FORMATS["tableTooDeep"] = "%s" .. DEVTOOLS_CUTOFF_COLOR .. "<table (too deep)>|r%s";
|
|
-- prefix value suffix
|
|
FORMATS["simpleValue"] = "%s%s%s";
|
|
-- prefix tablename suffix
|
|
FORMATS["tableReference"] = "%s" .. DEVTOOLS_TABLEREF_COLOR .. "%s|r%s";
|
|
|
|
-- Grab a copy various oft-used functions
|
|
local rawget = rawget;
|
|
local type = type;
|
|
local string_len = string.len;
|
|
local string_sub = string.sub;
|
|
local string_gsub = string.gsub;
|
|
local string_format = string.format;
|
|
local string_match = string.match;
|
|
|
|
local function WriteMessage(msg)
|
|
DEFAULT_CHAT_FRAME:AddMessage(msg);
|
|
end
|
|
|
|
local function prepSimple(val, context)
|
|
local valType = type(val);
|
|
if (valType == "nil") then
|
|
return "nil";
|
|
elseif (valType == "number") then
|
|
return val;
|
|
elseif (valType == "boolean") then
|
|
if (val) then
|
|
return "true";
|
|
else
|
|
return "false";
|
|
end
|
|
elseif (valType == "string") then
|
|
local l = string_len(val);
|
|
if ((l > DEVTOOLS_LONG_STRING_CUTOFF) and
|
|
(DEVTOOLS_LONG_STRING_CUTOFF > 0)) then
|
|
local more = l - DEVTOOLS_LONG_STRING_CUTOFF;
|
|
val = string_sub(val, 1, DEVTOOLS_LONG_STRING_CUTOFF);
|
|
return string_gsub(string_format("%q...+%s",val,more),"[|]", "||");
|
|
else
|
|
return string_gsub(string_format("%q",val),"[|]", "||");
|
|
end
|
|
elseif (valType == "function") then
|
|
local fName = context:GetFunctionName(val);
|
|
if (fName) then
|
|
return string_format(FORMATS.opaqueTypeKeyName, valType, fName);
|
|
else
|
|
return string_format(FORMATS.opaqueTypeKey, valType);
|
|
end
|
|
return string_format(FORMATS.opaqueTypeKey, valType);
|
|
elseif (valType == "userdata") then
|
|
local uName = context:GetUserdataName(val);
|
|
if (uName) then
|
|
return string_format(FORMATS.opaqueTypeKeyName, valType, uName);
|
|
else
|
|
return string_format(FORMATS.opaqueTypeKey, valType);
|
|
end
|
|
elseif (valType == 'table') then
|
|
local tName = context:GetTableName(val);
|
|
if (tName) then
|
|
return string_format(FORMATS.opaqueTypeKeyName, valType, tName);
|
|
else
|
|
return string_format(FORMATS.opaqueTypeKey, valType);
|
|
end
|
|
end
|
|
error("Bad type '" .. valType .. "' to prepSimple");
|
|
end
|
|
|
|
local function prepSimpleKey(val, context)
|
|
local valType = type(val);
|
|
if (valType == "string") then
|
|
local l = string_len(val);
|
|
if ((l <= DEVTOOLS_LONG_STRING_CUTOFF) or
|
|
(DEVTOOLS_LONG_STRING_CUTOFF <= 0)) then
|
|
if (string_match(val, "^[a-zA-Z_][a-zA-Z0-9_]*$")) then
|
|
return val;
|
|
end
|
|
end
|
|
end
|
|
return string_format(FORMATS.bracketTableKey, prepSimple(val, context));
|
|
end
|
|
|
|
local function DevTools_InitFunctionCache(context)
|
|
local ret = {};
|
|
|
|
for _,k in ipairs(DT.functionSymbols) do
|
|
local v = getglobal(k);
|
|
if (type(v) == 'function') then
|
|
ret[v] = '[' .. k .. ']';
|
|
end
|
|
end
|
|
|
|
for k,v in pairs(getfenv(0)) do
|
|
if (type(v) == 'function') then
|
|
if (not ret[v]) then
|
|
ret[v] = '[' .. k .. ']';
|
|
end
|
|
end
|
|
end
|
|
|
|
return ret;
|
|
end
|
|
|
|
local function DevTools_InitUserdataCache(context)
|
|
local ret = {};
|
|
|
|
for _,k in ipairs(DT.userdataSymbols) do
|
|
local v = getglobal(k);
|
|
if (type(v) == 'table') then
|
|
local u = rawget(v,0);
|
|
if (type(u) == 'userdata') then
|
|
ret[u] = k .. '[0]';
|
|
end
|
|
end
|
|
end
|
|
|
|
for k,v in pairs(getfenv(0)) do
|
|
if (type(v) == 'table') then
|
|
local u = rawget(v, 0);
|
|
if (type(u) == 'userdata') then
|
|
if (not ret[u]) then
|
|
ret[u] = k .. '[0]';
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return ret;
|
|
end
|
|
|
|
local function DevTools_Cache_Nil(self, value, newName)
|
|
return nil;
|
|
end
|
|
|
|
local function DevTools_Cache_Function(self, value, newName)
|
|
if (not self.fCache) then
|
|
self.fCache = DevTools_InitFunctionCache(self);
|
|
end
|
|
local name = self.fCache[value];
|
|
if ((not name) and newName) then
|
|
self.fCache[value] = newName;
|
|
end
|
|
return name;
|
|
end
|
|
|
|
local function DevTools_Cache_Userdata(self, value, newName)
|
|
if (not self.uCache) then
|
|
self.uCache = DevTools_InitUserdataCache(self);
|
|
end
|
|
local name = self.uCache[value];
|
|
if ((not name) and newName) then
|
|
self.uCache[value] = newName;
|
|
end
|
|
return name;
|
|
end
|
|
|
|
local function DevTools_Cache_Table(self, value, newName)
|
|
if (not self.tCache) then
|
|
self.tCache = {};
|
|
end
|
|
local name = self.tCache[value];
|
|
if ((not name) and newName) then
|
|
self.tCache[value] = newName;
|
|
end
|
|
return name;
|
|
end
|
|
|
|
local function DevTools_Write(self, msg)
|
|
DEFAULT_CHAT_FRAME:AddMessage(msg);
|
|
end
|
|
|
|
local DevTools_DumpValue;
|
|
|
|
local function DevTools_DumpTableContents(val, prefix, firstPrefix, context)
|
|
local showCount = 0;
|
|
local oldDepth = context.depth;
|
|
local oldKey = context.key;
|
|
|
|
-- Use this to set the cache name
|
|
context:GetTableName(val, oldKey or 'value');
|
|
|
|
local iter = pairs(val);
|
|
local nextK, nextV = iter(val, nil);
|
|
|
|
while (nextK) do
|
|
local k,v = nextK, nextV;
|
|
nextK, nextV = iter(val, k);
|
|
|
|
showCount = showCount + 1;
|
|
if ((showCount <= DEVTOOLS_MAX_ENTRY_CUTOFF) or
|
|
(DEVTOOLS_MAX_ENTRY_CUTOFF <= 0)) then
|
|
local prepKey = prepSimpleKey(k, context);
|
|
if (oldKey == nil) then
|
|
context.key = prepKey;
|
|
elseif (string_sub(prepKey, 1, 1) == "[") then
|
|
context.key = oldKey .. prepKey
|
|
else
|
|
context.key = oldKey .. "." .. prepKey
|
|
end
|
|
context.depth = oldDepth + 1;
|
|
|
|
local rp = string_format(FORMATS.tableKeyAssignPrefix, firstPrefix,
|
|
prepKey);
|
|
firstPrefix = prefix;
|
|
DevTools_DumpValue(v, prefix, rp,
|
|
(nextK and ",") or '',
|
|
context);
|
|
end
|
|
end
|
|
local cutoff = showCount - DEVTOOLS_MAX_ENTRY_CUTOFF;
|
|
if ((cutoff > 0) and (DEVTOOLS_MAX_ENTRY_CUTOFF > 0)) then
|
|
context:Write(string_format(FORMATS.tableEntriesSkipped,firstPrefix,
|
|
cutoff));
|
|
end
|
|
context.key = oldKey;
|
|
context.depth = oldDepth;
|
|
return (showCount > 0)
|
|
end
|
|
|
|
-- Return the specified value
|
|
function DevTools_DumpValue(val, prefix, firstPrefix, suffix, context)
|
|
local valType = type(val);
|
|
|
|
if (valType == "userdata") then
|
|
local uName = context:GetUserdataName(val, 'value');
|
|
if (uName) then
|
|
context:Write(string_format(FORMATS.opaqueTypeValName,
|
|
firstPrefix, valType, uName, suffix));
|
|
else
|
|
context:Write(string_format(FORMATS.opaqueTypeVal,
|
|
firstPrefix, valType, suffix));
|
|
end
|
|
return;
|
|
elseif (valType == "function") then
|
|
local fName = context:GetFunctionName(val, 'value');
|
|
if (fName) then
|
|
context:Write(string_format(FORMATS.opaqueTypeValName,
|
|
firstPrefix, valType, fName, suffix));
|
|
else
|
|
context:Write(string_format(FORMATS.opaqueTypeVal,
|
|
firstPrefix, valType, suffix));
|
|
end
|
|
return;
|
|
elseif (valType ~= "table") then
|
|
context:Write(string_format(FORMATS.simpleValue,
|
|
firstPrefix,prepSimple(val, context),
|
|
suffix));
|
|
return;
|
|
end
|
|
|
|
local cacheName = context:GetTableName(val);
|
|
if (cacheName) then
|
|
context:Write(string_format(FORMATS.tableReference,
|
|
firstPrefix, cacheName, suffix));
|
|
return;
|
|
end
|
|
|
|
if ((context.depth >= DEVTOOLS_DEPTH_CUTOFF) and
|
|
(DEVTOOLS_DEPTH_CUTOFF > 0)) then
|
|
context:Write(string_format(FORMATS.tableTooDeep,
|
|
firstPrefix, suffix));
|
|
return;
|
|
end
|
|
|
|
firstPrefix = firstPrefix .. "{";
|
|
local oldPrefix = prefix;
|
|
prefix = prefix .. DEVTOOLS_INDENT;
|
|
|
|
context:Write(firstPrefix);
|
|
firstPrefix = prefix;
|
|
local anyContents = DevTools_DumpTableContents(val, prefix, firstPrefix,
|
|
context);
|
|
context:Write(oldPrefix .. "}" .. suffix);
|
|
end
|
|
|
|
local function Pick_Cache_Function(func, setting)
|
|
if (setting) then
|
|
return func;
|
|
else
|
|
return DevTools_Cache_Nil;
|
|
end
|
|
end
|
|
|
|
function DevTools_RunDump(value, context)
|
|
local prefix = "";
|
|
local firstPrefix = prefix;
|
|
|
|
local valType = type(value);
|
|
if (type(value) == 'table') then
|
|
local any =
|
|
DevTools_DumpTableContents(value, prefix, firstPrefix, context);
|
|
if (context.Result) then
|
|
return context:Result();
|
|
end
|
|
if (not any) then
|
|
context:Write("empty result");
|
|
end
|
|
return;
|
|
end
|
|
|
|
DevTools_DumpValue(value, '', '', '', context);
|
|
if (context.Result) then
|
|
return context:Result();
|
|
end
|
|
end
|
|
|
|
-- Dump the specified list of value
|
|
function DevTools_Dump(value, startKey)
|
|
local context = {
|
|
depth = 0,
|
|
key = startKey,
|
|
};
|
|
|
|
context.GetTableName = Pick_Cache_Function(DevTools_Cache_Table,
|
|
DEVTOOLS_USE_TABLE_CACHE);
|
|
context.GetFunctionName = Pick_Cache_Function(DevTools_Cache_Function,
|
|
DEVTOOLS_USE_FUNCTION_CACHE);
|
|
context.GetUserdataName = Pick_Cache_Function(DevTools_Cache_Userdata,
|
|
DEVTOOLS_USE_USERDATA_CACHE);
|
|
context.Write = DevTools_Write;
|
|
|
|
DevTools_RunDump(value, context);
|
|
end
|
|
|
|
function DevTools_DumpCommand(msg, editBox)
|
|
forceinsecure();
|
|
if (string_match(msg,"^[A-Za-z_][A-Za-z0-9_]*$")) then
|
|
WriteMessage("Dump: " .. msg);
|
|
local val = _G[msg];
|
|
local tmp = {};
|
|
if (val == nil) then
|
|
local key = string_format(FORMATS.tableKeyAssignPrefix,
|
|
'', prepSimpleKey(msg, {}));
|
|
WriteMessage(key .. "nil,");
|
|
else
|
|
tmp[msg] = val;
|
|
end
|
|
DevTools_Dump(tmp);
|
|
return;
|
|
end
|
|
|
|
WriteMessage("Dump: value=" .. msg);
|
|
local func,err = loadstring("return " .. msg);
|
|
if (not func) then
|
|
WriteMessage("Dump: ERROR: " .. err);
|
|
else
|
|
DevTools_Dump({ func() }, "value");
|
|
end
|
|
end
|
|
|
|
DT.functionSymbols = {};
|
|
DT.userdataSymbols = {};
|
|
|
|
local funcSyms = DT.functionSymbols;
|
|
local userSyms = DT.userdataSymbols;
|
|
|
|
for k,v in pairs(getfenv(0)) do
|
|
if (type(v) == 'function') then
|
|
table.insert(funcSyms, k);
|
|
elseif (type(v) == 'table') then
|
|
if (type(rawget(v,0)) == 'userdata') then
|
|
table.insert(userSyms, k);
|
|
end
|
|
end
|
|
end
|
|
|