Skip to content
This repository was archived by the owner on Sep 2, 2019. It is now read-only.

Commit 5525508

Browse files
committed
an ultimate LUA vardump
1 parent 3aa20eb commit 5525508

File tree

1 file changed

+328
-0
lines changed

1 file changed

+328
-0
lines changed

tools/inspect.lua

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
local inspect ={
2+
_VERSION = 'inspect.lua 3.0.0',
3+
_URL = 'http://github.com/kikito/inspect.lua',
4+
_DESCRIPTION = 'human-readable representations of tables',
5+
_LICENSE = [[
6+
MIT LICENSE
7+
8+
Copyright (c) 2013 Enrique García Cota
9+
10+
Permission is hereby granted, free of charge, to any person obtaining a
11+
copy of this software and associated documentation files (the
12+
"Software"), to deal in the Software without restriction, including
13+
without limitation the rights to use, copy, modify, merge, publish,
14+
distribute, sublicense, and/or sell copies of the Software, and to
15+
permit persons to whom the Software is furnished to do so, subject to
16+
the following conditions:
17+
18+
The above copyright notice and this permission notice shall be included
19+
in all copies or substantial portions of the Software.
20+
21+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28+
]]
29+
}
30+
31+
inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
32+
inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
33+
34+
-- Apostrophizes the string if it has quotes, but not aphostrophes
35+
-- Otherwise, it returns a regular quoted string
36+
local function smartQuote(str)
37+
if str:match('"') and not str:match("'") then
38+
return "'" .. str .. "'"
39+
end
40+
return '"' .. str:gsub('"', '\\"') .. '"'
41+
end
42+
43+
local controlCharsTranslation = {
44+
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
45+
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
46+
}
47+
48+
local function escape(str)
49+
local result = str:gsub("\\", "\\\\"):gsub("(%c)", controlCharsTranslation)
50+
return result
51+
end
52+
53+
local function isIdentifier(str)
54+
return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
55+
end
56+
57+
local function isSequenceKey(k, length)
58+
return type(k) == 'number'
59+
and 1 <= k
60+
and k <= length
61+
and math.floor(k) == k
62+
end
63+
64+
local defaultTypeOrders = {
65+
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
66+
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
67+
}
68+
69+
local function sortKeys(a, b)
70+
local ta, tb = type(a), type(b)
71+
72+
-- strings and numbers are sorted numerically/alphabetically
73+
if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
74+
75+
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
76+
-- Two default types are compared according to the defaultTypeOrders table
77+
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
78+
elseif dta then return true -- default types before custom ones
79+
elseif dtb then return false -- custom types after default ones
80+
end
81+
82+
-- custom types are sorted out alphabetically
83+
return ta < tb
84+
end
85+
86+
local function getNonSequentialKeys(t)
87+
local keys, length = {}, #t
88+
for k,_ in pairs(t) do
89+
if not isSequenceKey(k, length) then table.insert(keys, k) end
90+
end
91+
table.sort(keys, sortKeys)
92+
return keys
93+
end
94+
95+
local function getToStringResultSafely(t, mt)
96+
local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')
97+
local str, ok
98+
if type(__tostring) == 'function' then
99+
ok, str = pcall(__tostring, t)
100+
str = ok and str or 'error: ' .. tostring(str)
101+
end
102+
if type(str) == 'string' and #str > 0 then return str end
103+
end
104+
105+
local maxIdsMetaTable = {
106+
__index = function(self, typeName)
107+
rawset(self, typeName, 0)
108+
return 0
109+
end
110+
}
111+
112+
local idsMetaTable = {
113+
__index = function (self, typeName)
114+
local col = setmetatable({}, {__mode = "kv"})
115+
rawset(self, typeName, col)
116+
return col
117+
end
118+
}
119+
120+
local function countTableAppearances(t, tableAppearances)
121+
tableAppearances = tableAppearances or setmetatable({}, {__mode = "k"})
122+
123+
if type(t) == 'table' then
124+
if not tableAppearances[t] then
125+
tableAppearances[t] = 1
126+
for k,v in pairs(t) do
127+
countTableAppearances(k, tableAppearances)
128+
countTableAppearances(v, tableAppearances)
129+
end
130+
countTableAppearances(getmetatable(t), tableAppearances)
131+
else
132+
tableAppearances[t] = tableAppearances[t] + 1
133+
end
134+
end
135+
136+
return tableAppearances
137+
end
138+
139+
local copySequence = function(s)
140+
local copy, len = {}, #s
141+
for i=1, len do copy[i] = s[i] end
142+
return copy, len
143+
end
144+
145+
local function makePath(path, ...)
146+
local keys = {...}
147+
local newPath, len = copySequence(path)
148+
for i=1, #keys do
149+
newPath[len + i] = keys[i]
150+
end
151+
return newPath
152+
end
153+
154+
local function processRecursive(process, item, path)
155+
if item == nil then return nil end
156+
157+
local processed = process(item, path)
158+
if type(processed) == 'table' then
159+
local processedCopy = {}
160+
local processedKey
161+
162+
for k,v in pairs(processed) do
163+
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY))
164+
if processedKey ~= nil then
165+
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey))
166+
end
167+
end
168+
169+
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE))
170+
setmetatable(processedCopy, mt)
171+
processed = processedCopy
172+
end
173+
return processed
174+
end
175+
176+
177+
-------------------------------------------------------------------
178+
179+
local Inspector = {}
180+
local Inspector_mt = {__index = Inspector}
181+
182+
function Inspector:puts(...)
183+
local args = {...}
184+
local buffer = self.buffer
185+
local len = #buffer
186+
for i=1, #args do
187+
len = len + 1
188+
buffer[len] = tostring(args[i])
189+
end
190+
end
191+
192+
function Inspector:down(f)
193+
self.level = self.level + 1
194+
f()
195+
self.level = self.level - 1
196+
end
197+
198+
function Inspector:tabify()
199+
self:puts(self.newline, string.rep(self.indent, self.level))
200+
end
201+
202+
function Inspector:alreadyVisited(v)
203+
return self.ids[type(v)][v] ~= nil
204+
end
205+
206+
function Inspector:getId(v)
207+
local tv = type(v)
208+
local id = self.ids[tv][v]
209+
if not id then
210+
id = self.maxIds[tv] + 1
211+
self.maxIds[tv] = id
212+
self.ids[tv][v] = id
213+
end
214+
return id
215+
end
216+
217+
function Inspector:putKey(k)
218+
if isIdentifier(k) then return self:puts(k) end
219+
self:puts("[")
220+
self:putValue(k)
221+
self:puts("]")
222+
end
223+
224+
function Inspector:putTable(t)
225+
if t == inspect.KEY or t == inspect.METATABLE then
226+
self:puts(tostring(t))
227+
elseif self:alreadyVisited(t) then
228+
self:puts('<table ', self:getId(t), '>')
229+
elseif self.level >= self.depth then
230+
self:puts('{...}')
231+
else
232+
if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
233+
234+
local nonSequentialKeys = getNonSequentialKeys(t)
235+
local length = #t
236+
local mt = getmetatable(t)
237+
local toStringResult = getToStringResultSafely(t, mt)
238+
239+
self:puts('{')
240+
self:down(function()
241+
if toStringResult then
242+
self:puts(' -- ', escape(toStringResult))
243+
if length >= 1 then self:tabify() end
244+
end
245+
246+
local count = 0
247+
for i=1, length do
248+
if count > 0 then self:puts(',') end
249+
self:puts(' ')
250+
self:putValue(t[i])
251+
count = count + 1
252+
end
253+
254+
for _,k in ipairs(nonSequentialKeys) do
255+
if count > 0 then self:puts(',') end
256+
self:tabify()
257+
self:putKey(k)
258+
self:puts(' = ')
259+
self:putValue(t[k])
260+
count = count + 1
261+
end
262+
263+
if mt then
264+
if count > 0 then self:puts(',') end
265+
self:tabify()
266+
self:puts('<metatable> = ')
267+
self:putValue(mt)
268+
end
269+
end)
270+
271+
if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing }
272+
self:tabify()
273+
elseif length > 0 then -- array tables have one extra space before closing }
274+
self:puts(' ')
275+
end
276+
277+
self:puts('}')
278+
end
279+
end
280+
281+
function Inspector:putValue(v)
282+
local tv = type(v)
283+
284+
if tv == 'string' then
285+
self:puts(smartQuote(escape(v)))
286+
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then
287+
self:puts(tostring(v))
288+
elseif tv == 'table' then
289+
self:putTable(v)
290+
else
291+
self:puts('<',tv,' ',self:getId(v),'>')
292+
end
293+
end
294+
295+
-------------------------------------------------------------------
296+
297+
function inspect.inspect(root, options)
298+
options = options or {}
299+
300+
local depth = options.depth or math.huge
301+
local newline = options.newline or '\n'
302+
local indent = options.indent or ' '
303+
local process = options.process
304+
305+
if process then
306+
root = processRecursive(process, root, {})
307+
end
308+
309+
local inspector = setmetatable({
310+
depth = depth,
311+
buffer = {},
312+
level = 0,
313+
ids = setmetatable({}, idsMetaTable),
314+
maxIds = setmetatable({}, maxIdsMetaTable),
315+
newline = newline,
316+
indent = indent,
317+
tableAppearances = countTableAppearances(root)
318+
}, Inspector_mt)
319+
320+
inspector:putValue(root)
321+
322+
return table.concat(inspector.buffer)
323+
end
324+
325+
setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
326+
327+
return inspect
328+

0 commit comments

Comments
 (0)