local inspect = { _VERSION = 'inspect.lua 3.1.0', _URL = 'http://github.com/kikito/inspect.lua', _DESCRIPTION = 'human-readable representations of tables', } local tostring = tostring inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end, }) inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end, }) local function rawpairs(t) return next, t, nil end -- Apostrophizes the string if it has quotes, but not aphostrophes -- Otherwise, it returns a regular quoted string local function smartQuote(str) if str:match('"') and not str:match("'") then return "'" .. str .. "'" end return '"' .. str:gsub('"', '\\"') .. '"' end -- \a => '\\a', \0 => '\\0', 31 => '\31' local shortControlCharEscapes = { ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", } local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031 for i = 0, 31 do local ch = string.char(i) if not shortControlCharEscapes[ch] then shortControlCharEscapes[ch] = "\\" .. i longControlCharEscapes[ch] = string.format("\\%03d", i) end end local function escape(str) return (str:gsub("\\", "\\\\"):gsub("(%c)%f[0-9]", longControlCharEscapes):gsub("%c", shortControlCharEscapes)) end local function isIdentifier(str) return type(str) == 'string' and str:match("^[_%a][_%a%d]*$") end local function isSequenceKey(k, sequenceLength) return type(k) == 'number' and 1 <= k and k <= sequenceLength and math.floor(k) == k end local defaultTypeOrders = { ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, ['function'] = 5, ['userdata'] = 6, ['thread'] = 7, } local function sortKeys(a, b) local ta, tb = type(a), type(b) -- strings and numbers are sorted numerically/alphabetically if ta == tb and (ta == 'string' or ta == 'number') then return a < b end local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] -- Two default types are compared according to the defaultTypeOrders table if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] elseif dta then return true -- default types before custom ones elseif dtb then return false -- custom types after default ones end -- custom types are sorted out alphabetically return ta < tb end -- For implementation reasons, the behavior of rawlen & # is "undefined" when -- tables aren't pure sequences. So we implement our own # operator. local function getSequenceLength(t) local len = 1 local v = rawget(t, len) while v ~= nil do len = len + 1 v = rawget(t, len) end return len - 1 end local function getNonSequentialKeys(t) local keys, keysLength = {}, 0 local sequenceLength = getSequenceLength(t) for k, _ in rawpairs(t) do if not isSequenceKey(k, sequenceLength) then keysLength = keysLength + 1 keys[keysLength] = k end end table.sort(keys, sortKeys) return keys, keysLength, sequenceLength end local function countTableAppearances(t, tableAppearances) tableAppearances = tableAppearances or {} if type(t) == 'table' then if not tableAppearances[t] then tableAppearances[t] = 1 for k, v in rawpairs(t) do countTableAppearances(k, tableAppearances) countTableAppearances(v, tableAppearances) end countTableAppearances(getmetatable(t), tableAppearances) else tableAppearances[t] = tableAppearances[t] + 1 end end return tableAppearances end local copySequence = function(s) local copy, len = {}, #s for i = 1, len do copy[i] = s[i] end return copy, len end local function makePath(path, ...) local keys = {...} local newPath, len = copySequence(path) for i = 1, #keys do newPath[len + i] = keys[i] end return newPath end local function processRecursive(process, item, path, visited) if item == nil then return nil end if visited[item] then return visited[item] end local processed = process(item, path) if type(processed) == 'table' then local processedCopy = {} visited[item] = processedCopy local processedKey for k, v in rawpairs(processed) do processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) if processedKey ~= nil then processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) end end local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field setmetatable(processedCopy, mt) processed = processedCopy end return processed end ------------------------------------------------------------------- local Inspector = {} local Inspector_mt = { __index = Inspector, } function Inspector:puts(...) local args = {...} local buffer = self.buffer local len = #buffer for i = 1, #args do len = len + 1 buffer[len] = args[i] end end function Inspector:down(f) self.level = self.level + 1 f() self.level = self.level - 1 end function Inspector:tabify() self:puts(self.newline, string.rep(self.indent, self.level)) end function Inspector:alreadyVisited(v) return self.ids[v] ~= nil end function Inspector:getId(v) local id = self.ids[v] if not id then local tv = type(v) id = (self.maxIds[tv] or 0) + 1 self.maxIds[tv] = id self.ids[v] = id end return tostring(id) end function Inspector:putKey(k) if isIdentifier(k) then return self:puts(k) end self:puts("[") self:putValue(k) self:puts("]") end function Inspector:putTable(t) if t == inspect.KEY or t == inspect.METATABLE then self:puts(tostring(t)) elseif self:alreadyVisited(t) then self:puts('