inspect.lua 8.9 KB

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