Advertisement
wolfd

Untitled

Aug 1st, 2014
227
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.44 KB | None | 0 0
  1. local inspect ={
  2.   _VERSION = 'inspect.lua 2.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. -- Apostrophizes the string if it has quotes, but not aphostrophes
  32. -- Otherwise, it returns a regular quoted string
  33. local function smartQuote(str)
  34.   if str:match('"') and not str:match("'") then
  35.     return "'" .. str .. "'"
  36.   end
  37.   return '"' .. str:gsub('"', '\\"') .. '"'
  38. end
  39.  
  40. local controlCharsTranslation = {
  41.   ["\a"] = "\\a",  ["\b"] = "\\b", ["\f"] = "\\f",  ["\n"] = "\\n",
  42.   ["\r"] = "\\r",  ["\t"] = "\\t", ["\v"] = "\\v"
  43. }
  44.  
  45. local function escapeChar(c) return controlCharsTranslation[c] end
  46.  
  47. local function escape(str)
  48.   local result = str:gsub("\\", "\\\\"):gsub("(%c)", escapeChar)
  49.   return result
  50. end
  51.  
  52. local function isIdentifier(str)
  53.   return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
  54. end
  55.  
  56. local function isArrayKey(k, length)
  57.   return type(k) == 'number' and 1 <= k and k <= length
  58. end
  59.  
  60. local function isDictionaryKey(k, length)
  61.   return not isArrayKey(k, length)
  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 getDictionaryKeys(t)
  87.   local keys, length = {}, #t
  88.   for k,_ in pairs(t) do
  89.     if isDictionaryKey(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 function parse_filter(filter)
  140.   if type(filter) == 'function' then return filter end
  141.   -- not a function, so it must be a table or table-like
  142.   filter = type(filter) == 'table' and filter or {filter}
  143.   local dictionary = {}
  144.   for _,v in pairs(filter) do dictionary[v] = true end
  145.   return function(x) return dictionary[x] end
  146. end
  147.  
  148. local function makePath(path, key)
  149.   local newPath, len = {}, #path
  150.   for i=1, len do newPath[i] = path[i] end
  151.   newPath[len+1] = key
  152.   return newPath
  153. end
  154.  
  155. -------------------------------------------------------------------
  156. function inspect.inspect(rootObject, options)
  157.   options       = options or {}
  158.   local depth   = options.depth or math.huge
  159.   local filter  = parse_filter(options.filter or {})
  160.  
  161.   local tableAppearances = countTableAppearances(rootObject)
  162.  
  163.   local buffer = {}
  164.   local maxIds = setmetatable({}, maxIdsMetaTable)
  165.   local ids    = setmetatable({}, idsMetaTable)
  166.   local level  = 0
  167.   local blen   = 0 -- buffer length
  168.  
  169.   local function puts(...)
  170.     local args = {...}
  171.     for i=1, #args do
  172.       blen = blen + 1
  173.       buffer[blen] = tostring(args[i])
  174.     end
  175.   end
  176.  
  177.   local function down(f)
  178.     level = level + 1
  179.     f()
  180.     level = level - 1
  181.   end
  182.  
  183.   local function tabify()
  184.     puts("\n", string.rep("  ", level))
  185.   end
  186.  
  187.   local function commaControl(needsComma)
  188.     if needsComma then puts(',') end
  189.     return true
  190.   end
  191.  
  192.   local function alreadyVisited(v)
  193.     return ids[type(v)][v] ~= nil
  194.   end
  195.  
  196.   local function getId(v)
  197.     local tv = type(v)
  198.     local id = ids[tv][v]
  199.     if not id then
  200.       id         = maxIds[tv] + 1
  201.       maxIds[tv] = id
  202.       ids[tv][v] = id
  203.     end
  204.     return id
  205.   end
  206.  
  207.   local putValue -- forward declaration that needs to go before putTable & putKey
  208.  
  209.   local function putKey(k)
  210.     if isIdentifier(k) then return puts(k) end
  211.     puts( "[" )
  212.     putValue(k, {})
  213.     puts("]")
  214.   end
  215.  
  216.   local function putTable(t, path)
  217.     if alreadyVisited(t) then
  218.       puts('<table ', getId(t), '>')
  219.     elseif level >= depth then
  220.       puts('{...}')
  221.     else
  222.       if tableAppearances[t] > 1 then puts('<', getId(t), '>') end
  223.  
  224.       local dictKeys          = getDictionaryKeys(t)
  225.       local length            = #t
  226.       local mt                = getmetatable(t)
  227.       local to_string_result  = getToStringResultSafely(t, mt)
  228.  
  229.       puts('{')
  230.       down(function()
  231.         if to_string_result then
  232.           puts(' -- ', escape(to_string_result))
  233.           if length >= 1 then tabify() end -- tabify the array values
  234.         end
  235.  
  236.         local needsComma = false
  237.         for i=1, length do
  238.           needsComma = commaControl(needsComma)
  239.           puts(' ')
  240.           putValue(t[i], makePath(path, i))
  241.         end
  242.  
  243.         for _,k in ipairs(dictKeys) do
  244.           needsComma = commaControl(needsComma)
  245.           tabify()
  246.           putKey(k)
  247.           puts(' = ')
  248.           putValue(t[k], makePath(path, k))
  249.         end
  250.  
  251.         if mt then
  252.           needsComma = commaControl(needsComma)
  253.           tabify()
  254.           puts('<metatable> = ')
  255.           putValue(mt, makePath(path, '<metatable>'))
  256.         end
  257.       end)
  258.  
  259.       if #dictKeys > 0 or mt then -- dictionary table. Justify closing }
  260.         tabify()
  261.       elseif length > 0 then -- array tables have one extra space before closing }
  262.         puts(' ')
  263.       end
  264.  
  265.       puts('}')
  266.     end
  267.  
  268.   end
  269.  
  270.   -- putvalue is forward-declared before putTable & putKey
  271.   putValue = function(v, path)
  272.     if filter(v, path) then
  273.       puts('<filtered>')
  274.     else
  275.       local tv = type(v)
  276.  
  277.       if tv == 'string' then
  278.         puts(smartQuote(escape(v)))
  279.       elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then
  280.         puts(tostring(v))
  281.       elseif tv == 'table' then
  282.         putTable(v, path)
  283.       else
  284.         puts('<',tv,' ',getId(v),'>')
  285.       end
  286.     end
  287.   end
  288.  
  289.   putValue(rootObject, {})
  290.  
  291.   return table.concat(buffer)
  292. end
  293.  
  294. setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
  295.  
  296. return inspect
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement