1+ local _tl_compat ; if (tonumber ((_VERSION or ' ' ):match (' [%d.]*$' )) or 0 ) < 5.3 then local p , m = pcall (require , ' compat53.module' ); if p then _tl_compat = m end end ; local math = _tl_compat and _tl_compat .math or math ; local string = _tl_compat and _tl_compat .string or string ; local table = _tl_compat and _tl_compat .table or table
2+ local inspect = {Options = {}, }
3+
4+
5+
6+
7+
8+
9+
10+
11+
12+
13+
14+
15+
16+
17+
18+
19+
20+ inspect ._VERSION = ' inspect.lua 3.1.0'
21+ inspect ._URL = ' http://github.com/kikito/inspect.lua'
22+ inspect ._DESCRIPTION = ' human-readable representations of tables'
23+ inspect ._LICENSE = [[
24+ MIT LICENSE
25+ Copyright (c) 2022 Enrique García Cota
26+ Permission is hereby granted, free of charge, to any person obtaining a
27+ copy of this software and associated documentation files (the
28+ "Software"), to deal in the Software without restriction, including
29+ without limitation the rights to use, copy, modify, merge, publish,
30+ distribute, sublicense, and/or sell copies of the Software, and to
31+ permit persons to whom the Software is furnished to do so, subject to
32+ the following conditions:
33+ The above copyright notice and this permission notice shall be included
34+ in all copies or substantial portions of the Software.
35+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
36+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
37+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
38+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
39+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
40+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
41+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42+ ]]
43+ inspect .KEY = setmetatable ({}, { __tostring = function () return ' inspect.KEY' end })
44+ inspect .METATABLE = setmetatable ({}, { __tostring = function () return ' inspect.METATABLE' end })
45+
46+ local tostring = tostring
47+ local rep = string.rep
48+ local match = string.match
49+ local char = string.char
50+ local gsub = string.gsub
51+ local fmt = string.format
52+
53+ local function rawpairs (t )
54+ return next , t , nil
55+ end
56+
57+
58+
59+ local function smartQuote (str )
60+ if match (str , ' "' ) and not match (str , " '" ) then
61+ return " '" .. str .. " '"
62+ end
63+ return ' "' .. gsub (str , ' "' , ' \\ "' ) .. ' "'
64+ end
65+
66+
67+ local shortControlCharEscapes = {
68+ [" \a " ] = " \\ a" , [" \b " ] = " \\ b" , [" \f " ] = " \\ f" , [" \n " ] = " \\ n" ,
69+ [" \r " ] = " \\ r" , [" \t " ] = " \\ t" , [" \v " ] = " \\ v" , [" \127 " ] = " \\ 127" ,
70+ }
71+ local longControlCharEscapes = { [" \127 " ] = " \127 " }
72+ for i = 0 , 31 do
73+ local ch = char (i )
74+ if not shortControlCharEscapes [ch ] then
75+ shortControlCharEscapes [ch ] = " \\ " .. i
76+ longControlCharEscapes [ch ] = fmt (" \\ %03d" , i )
77+ end
78+ end
79+
80+ local function escape (str )
81+ return (gsub (gsub (gsub (str , " \\ " , " \\\\ " ),
82+ " (%c)%f[0-9]" , longControlCharEscapes ),
83+ " %c" , shortControlCharEscapes ))
84+ end
85+
86+ local function isIdentifier (str )
87+ return type (str ) == " string" and not not str :match (" ^[_%a][_%a%d]*$" )
88+ end
89+
90+ local flr = math.floor
91+ local function isSequenceKey (k , sequenceLength )
92+ return type (k ) == " number" and
93+ flr (k ) == k and
94+ 1 <= (k ) and
95+ k <= sequenceLength
96+ end
97+
98+ local defaultTypeOrders = {
99+ [' number' ] = 1 , [' boolean' ] = 2 , [' string' ] = 3 , [' table' ] = 4 ,
100+ [' function' ] = 5 , [' userdata' ] = 6 , [' thread' ] = 7 ,
101+ }
102+
103+ local function sortKeys (a , b )
104+ local ta , tb = type (a ), type (b )
105+
106+
107+ if ta == tb and (ta == ' string' or ta == ' number' ) then
108+ return (a ) < (b )
109+ end
110+
111+ local dta = defaultTypeOrders [ta ] or 100
112+ local dtb = defaultTypeOrders [tb ] or 100
113+
114+
115+ return dta == dtb and ta < tb or dta < dtb
116+ end
117+
118+ local function getKeys (t )
119+
120+ local seqLen = 1
121+ while rawget (t , seqLen ) ~= nil do
122+ seqLen = seqLen + 1
123+ end
124+ seqLen = seqLen - 1
125+
126+ local keys , keysLen = {}, 0
127+ for k in rawpairs (t ) do
128+ if not isSequenceKey (k , seqLen ) then
129+ keysLen = keysLen + 1
130+ keys [keysLen ] = k
131+ end
132+ end
133+ table.sort (keys , sortKeys )
134+ return keys , keysLen , seqLen
135+ end
136+
137+ local function countCycles (x , cycles )
138+ if type (x ) == " table" then
139+ if cycles [x ] then
140+ cycles [x ] = cycles [x ] + 1
141+ else
142+ cycles [x ] = 1
143+ for k , v in rawpairs (x ) do
144+ countCycles (k , cycles )
145+ countCycles (v , cycles )
146+ end
147+ countCycles (getmetatable (x ), cycles )
148+ end
149+ end
150+ end
151+
152+ local function makePath (path , a , b )
153+ local newPath = {}
154+ local len = # path
155+ for i = 1 , len do newPath [i ] = path [i ] end
156+
157+ newPath [len + 1 ] = a
158+ newPath [len + 2 ] = b
159+
160+ return newPath
161+ end
162+
163+
164+ local function processRecursive (process ,
165+ item ,
166+ path ,
167+ visited )
168+ if item == nil then return nil end
169+ if visited [item ] then return visited [item ] end
170+
171+ local processed = process (item , path )
172+ if type (processed ) == " table" then
173+ local processedCopy = {}
174+ visited [item ] = processedCopy
175+ local processedKey
176+
177+ for k , v in rawpairs (processed ) do
178+ processedKey = processRecursive (process , k , makePath (path , k , inspect .KEY ), visited )
179+ if processedKey ~= nil then
180+ processedCopy [processedKey ] = processRecursive (process , v , makePath (path , processedKey ), visited )
181+ end
182+ end
183+
184+ local mt = processRecursive (process , getmetatable (processed ), makePath (path , inspect .METATABLE ), visited )
185+ if type (mt ) ~= ' table' then mt = nil end
186+ setmetatable (processedCopy , mt )
187+ processed = processedCopy
188+ end
189+ return processed
190+ end
191+
192+ local function puts (buf , str )
193+ buf .n = buf .n + 1
194+ buf [buf .n ] = str
195+ end
196+
197+
198+
199+ local Inspector = {}
200+
201+
202+
203+
204+
205+
206+
207+
208+
209+
210+ local Inspector_mt = { __index = Inspector }
211+
212+ local function tabify (inspector )
213+ puts (inspector .buf , inspector .newline .. rep (inspector .indent , inspector .level ))
214+ end
215+
216+ function Inspector :getId (v )
217+ local id = self .ids [v ]
218+ local ids = self .ids
219+ if not id then
220+ local tv = type (v )
221+ id = (ids [tv ] or 0 ) + 1
222+ ids [v ], ids [tv ] = id , id
223+ end
224+ return tostring (id )
225+ end
226+
227+ function Inspector :putValue (v )
228+ local buf = self .buf
229+ local tv = type (v )
230+ if tv == ' string' then
231+ puts (buf , smartQuote (escape (v )))
232+ elseif tv == ' number' or tv == ' boolean' or tv == ' nil' or
233+ tv == ' cdata' or tv == ' ctype' then
234+ puts (buf , tostring (v ))
235+ elseif tv == ' table' and not self .ids [v ] then
236+ local t = v
237+
238+ if t == inspect .KEY or t == inspect .METATABLE then
239+ puts (buf , tostring (t ))
240+ elseif self .level >= self .depth then
241+ puts (buf , ' {...}' )
242+ else
243+ if self .cycles [t ] > 1 then puts (buf , fmt (' <%d>' , self :getId (t ))) end
244+
245+ local keys , keysLen , seqLen = getKeys (t )
246+
247+ puts (buf , ' {' )
248+ self .level = self .level + 1
249+
250+ for i = 1 , seqLen + keysLen do
251+ if i > 1 then puts (buf , ' ,' ) end
252+ if i <= seqLen then
253+ puts (buf , ' ' )
254+ self :putValue (t [i ])
255+ else
256+ local k = keys [i - seqLen ]
257+ tabify (self )
258+ if isIdentifier (k ) then
259+ puts (buf , k )
260+ else
261+ puts (buf , " [" )
262+ self :putValue (k )
263+ puts (buf , " ]" )
264+ end
265+ puts (buf , ' = ' )
266+ self :putValue (t [k ])
267+ end
268+ end
269+
270+ local mt = getmetatable (t )
271+ if type (mt ) == ' table' then
272+ if seqLen + keysLen > 0 then puts (buf , ' ,' ) end
273+ tabify (self )
274+ puts (buf , ' <metatable> = ' )
275+ self :putValue (mt )
276+ end
277+
278+ self .level = self .level - 1
279+
280+ if keysLen > 0 or type (mt ) == ' table' then
281+ tabify (self )
282+ elseif seqLen > 0 then
283+ puts (buf , ' ' )
284+ end
285+
286+ puts (buf , ' }' )
287+ end
288+
289+ else
290+ puts (buf , fmt (' <%s %d>' , tv , self :getId (v )))
291+ end
292+ end
293+
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 cycles = {}
310+ countCycles (root , cycles )
311+
312+ local inspector = setmetatable ({
313+ buf = { n = 0 },
314+ ids = {},
315+ cycles = cycles ,
316+ depth = depth ,
317+ level = 0 ,
318+ newline = newline ,
319+ indent = indent ,
320+ }, Inspector_mt )
321+
322+ inspector :putValue (root )
323+
324+ return table.concat (inspector .buf )
325+ end
326+
327+ setmetatable (inspect , {
328+ __call = function (_ , root , options )
329+ return inspect .inspect (root , options )
330+ end ,
331+ })
332+
333+ return inspect
0 commit comments