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