Giter Club home page Giter Club logo

inspect.lua's Introduction

inspect.lua

This library transforms any Lua value into a human-readable representation. It is especially useful for debugging errors in tables.

The objective here is human understanding (i.e. for debugging), not serialization or compactness.

Examples of use

inspect has the following declaration: local str = inspect(value, <options>).

value can be any Lua value.

inspect transforms simple types (like strings or numbers) into strings.

assert(inspect(1) == "1")
assert(inspect("Hello") == '"Hello"')

Tables, on the other hand, are rendered in a way a human can read easily.

"Array-like" tables are rendered horizontally:

assert(inspect({1,2,3,4}) == "{ 1, 2, 3, 4 }")

"Dictionary-like" tables are rendered with one element per line:

assert(inspect({a=1,b=2}) == [[{
  a = 1,
  b = 2
}]])

The keys will be sorted alphanumerically when possible.

"Hybrid" tables will have the array part on the first line, and the dictionary part just below them:

assert(inspect({1,2,3,b=2,a=1}) == [[{ 1, 2, 3,
  a = 1,
  b = 2
}]])

Subtables are indented with two spaces per level.

assert(inspect({a={b=2}}) == [[{
  a = {
    b = 2
  }
}]])

Functions, userdata and any other custom types from Luajit are simply as <function x>, <userdata x>, etc.:

assert(inspect({ f = print, ud = some_user_data, thread = a_thread} ) == [[{
  f = <function 1>,
  u = <userdata 1>,
  thread = <thread 1>
}]])

If the table has a metatable, inspect will include it at the end, in a special field called <metatable>:

assert(inspect(setmetatable({a=1}, {b=2}) == [[{
  a = 1
  <metatable> = {
    b = 2
  }
}]]))

inspect can handle tables with loops inside them. It will print <id> right before the table is printed out the first time, and replace the whole table with <table id> from then on, preventing infinite loops.

local a = {1, 2}
local b = {3, 4, a}
a[3] = b -- a references b, and b references a
assert(inspect(a) == "<1>{ 1, 2, { 3, 4, <table 1> } }")

Notice that since both a appears more than once in the expression, it is prefixed by <1> and replaced by <table 1> every time it appears later on.

options

inspect has a second parameter, called options. It is not mandatory, but when it is provided, it must be a table.

options.depth

options.depth sets the maximum depth that will be printed out. When the max depth is reached, inspect will stop parsing tables and just return {...}:

local t5 = {a = {b = {c = {d = {e = 5}}}}}

assert(inspect(t5, {depth = 4}) == [[{
  a = {
    b = {
      c = {
        d = {...}
      }
    }
  }
}]])

assert(inspect(t5, {depth = 2}) == [[{
  a = {
    b = {...}
  }
}]])

options.depth defaults to infinite (math.huge).

options.newline & options.indent

These are the strings used by inspect to respectively add a newline and indent each level of a table.

By default, options.newline is "\n" and options.indent is " " (two spaces).

local t = {a={b=1}}

assert(inspect(t) == [[{
  a = {
    b = 1
  }
}]])

assert(inspect(t, {newline='@', indent="++"}), "{@++a = {@++++b = 1@++}@}"

options.process

options.process is a function which allow altering the passed object before transforming it into a string. A typical way to use it would be to remove certain values so that they don't appear at all.

options.process has the following signature:

local processed_item = function(item, path)
  • item is either a key or a value on the table, or any of its subtables
  • path is an array-like table built with all the keys that have been used to reach item, from the root.
    • For values, it is just a regular list of keys. For example, to reach the 1 in {a = {b = 1}}, the path will be {'a', 'b'}
    • For keys, the special value inspect.KEY is inserted. For example, to reach the c in {a = {b = {c = 1}}}, the path will be {'a', 'b', 'c', inspect.KEY }
    • For metatables, the special value inspect.METATABLE is inserted. For {a = {b = 1}}}, the path {'a', {b = 1}, inspect.METATABLE} means "the metatable of the table {b = 1}".
  • processed_item is the value returned by options.process. If it is equal to item, then the inspected table will look unchanged. If it is different, then the table will look different; most notably, if it's nil, the item will dissapear on the inspected table.

Examples

Remove a particular metatable from the result:

local t = {1,2,3}
local mt = {b = 2}
setmetatable(t, mt)

local remove_mt = function(item)
  if item ~= mt then return item end
end

-- mt does not appear
assert(inspect(t, {process = remove_mt}) == "{ 1, 2, 3 }")

The previous example only works for a particular metatable. If you want to make all metatables, you can use the path parameter to check wether the last element is inspect.METATABLE, and return nil instead of the item:

local t, mt = ... -- (defined as before)

local remove_all_metatables = function(item, path)
  if path[#path] ~= inspect.METATABLE then return item end
end

assert(inspect(t, {process = remove_all_metatables}) == "{ 1, 2, 3 }")

Filter a value:

local anonymize_password = function(item, path)
  if path[#path] == 'password' then return "XXXX" end
  return item
end

local info = {user = 'peter', password = 'secret'}

assert(inspect(info, {process = anonymize_password}) == [[{
  password = "XXXX",
  user     = "peter"
}]])

Gotchas / Warnings

This method is not appropriate for saving/restoring tables. It is meant to be used by the programmer mainly while debugging a program.

Installation

If you are using luarocks, just run

luarocks install inspect

Otherwise, you can just copy the inspect.lua file somewhere in your projects (maybe inside a /lib/ folder) and require it accordingly.

Remember to store the value returned by require somewhere! (I suggest a local variable named inspect, although others might like table.inspect)

local inspect = require 'inspect'
      -- or --
local inspect = require 'lib.inspect'

Also, make sure to read the license; the text of that license file must appear somewhere in your projects' files. For your convenience, it's included at the begining of inspect.lua.

Contributing

This project uses Teal, a typed dialect of Lua (which generates plain lua files too)

If you want to send a pull request to this project, first of all, thank you! You will need to install the. You can install all of them by running:

make dev

When writing your PR, please make your modifications on the inspect.tl file and then generate the inspect.lua file from it. You will probably want to make sure that the tests are still working (github should run them from you, but they should run very fast). You can do both things in one go by just invoking

make

This will generate inspect.lua, check it with luacheck and then launch busted to run the specs.

If you are sending a pull request, you might want to add some specs in the specs folder.

Change log

Read it on the CHANGELOG.md file

inspect.lua's People

Contributors

akopytov avatar andreashofer123 avatar cheyilin avatar cota avatar ignacio avatar intendedconsequence avatar kikito avatar kodiologist avatar mpeterv avatar soliton- avatar svvac avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

inspect.lua's Issues

Use dofile in Gideros

I couldn't use require in Gideros.

This works:

local inspect = dofile("inspect.lua")

At the moment I'm just investigating the structure of some generated tables, but I can see this is a valuable tool for validation. So I'm not going to create a PR, just in case there are other differences.

support userdata

If inspect can inspect userdata, will be wonderful.

for example a userdata point, point.x = 5, point.y = 6, we can inspect the userdata from metatable getters.

inspect(point) = <userdata id> {
   x = 5
   y = 6
}
    meta = getmetatable(userdata)
    meta['get']

Buggy escape in smartQuote

I added two tests, both are failing:

    it('escapes unbalanced double-quote', function()
      assert.equals([['I have a \" quote']], inspect([[I have a " quote]]))
    end)

    it('escapes backslashed unbalanced double-quote', function()
      assert.equals([['I have a \" quote']], inspect([[I have a backslashed \" quote]]))
    end)

Test run with "busted" (note busted always adds single-quotes around strings):

Failure → spec/inspect_spec.lua @ 29
inspect strings escapes unbalanced double-quote
spec/inspect_spec.lua:30: Expected objects to be equal.
Passed in:
(string) ''I have a " quote''
Expected:
(string) ''I have a \" quote''

Failure → spec/inspect_spec.lua @ 33
inspect strings escapes backslashed unbalanced double-quote
spec/inspect_spec.lua:34: Expected objects to be equal.
Passed in:
(string) ''I have a backslashed \\" quote''
Expected:
(string) ''I have a \" quote''

My suggestion is breaking backwards-compatibility in case someone improperly relied on exact string representations (like inspect's own tests). I have written a table serializer in Lua before, the go-to function for this purpose is string.format with the %q parameter.

Lua 5.1's doc was easier to understand:

there is an extra option, q. The q option formats a string in a form suitable to be safely read back by the Lua interpreter: the string is written between double quotes, and all double quotes, newlines, embedded zeros, and backslashes in the string are correctly escaped when written. For instance, the call

 string.format('%q', 'a string with "quotes" and \n new line')

will produce the string:

"a string with \"quotes\" and \
 new line"

But Lua 5.4's doc is more complete and correct:

The specifier q formats booleans, nil, numbers, and strings in a way that the result is a valid constant in Lua source code. Booleans and nil are written in the obvious way (true, false, nil). Floats are written in hexadecimal, to preserve full precision. A string is written between double quotes, using escape sequences when necessary to ensure that it can safely be read back by the Lua interpreter. For instance, the call ...

Technically it's the only correct way to serialize floats, to a binary/hexadecimal representation. I can make that it's own issue if you care. For strings it's either do-it-yourself (including bugs as right now :)), use %q, or use easier to implement [==[long literal string]==] syntax where you would only need to take care of special newline parsing and the double bracket syntax.

  • bracket syntax cons: since Lua is 8-bit clean in strings i.e. doesn't care about binary data in strings, this would always include binary data as-is in the output
  • %q cons: more verbose. Pros: but we can still pretty-print escapes like \t \n \127 and so on, so the string stays on a single line and doesn't contain binary characters

I can make a PR if you agree on a variant.


The smartQuote I mentioned:

   if tv == 'string' then
      puts(buf, smartQuote(escape(v)))

inspect.lua/inspect.lua

Lines 70 to 75 in 8686162

local function smartQuote(str)
if match(str, '"') and not match(str, "'") then
return "'" .. str .. "'"
end
return '"' .. gsub(str, '"', '\\"') .. '"'
end

Idea: integration with middleclass

Hello.

I've been using middleclass and inspect for a long time, but today I came up with idea and want to hear your feedback :)

Currently, if you use a complex middleclass type with inspect, you get something like this:

{
  class = {
    __declaredMethods = {
      __tostring = <function 1>,
      getTile = <function 2>,
      initialize = <function 3>,
      isInstanceOf = <function 4>,
      loadFromJSON = <function 5>,
      setTile = <function 6>
    },
    __instanceDict = <1>{
      __index = <table 1>,
      __tostring = <function 1>,
      getTile = <function 2>,
      initialize = <function 3>,
      isInstanceOf = <function 4>,
      loadFromJSON = <function 5>,
      setTile = <function 6>
    },
    name = "TileMapLayer",
    static = <2>{
      allocate = <function 7>,
      include = <function 8>,
      isSubclassOf = <function 9>,
      new = <function 10>,
      subclass = <function 11>,
      subclassed = <function 12>,
      <metatable> = {
        __index = <function 13>
      }
    },
    subclasses = {
      <metatable> = {
        __mode = "k"
      }
    },
    <metatable> = {
      __call = <function 14>,
      __index = <table 2>,
      __newindex = <function 15>,
      __tostring = <function 16>
    }
  },
  tiles = {
    ["0,0"] = {
      class = <3>{
        __declaredMethods = {
          __tostring = <function 1>,
          initialize = <function 17>,
          isInstanceOf = <function 4>
        },
        __instanceDict = <4>{
          __index = <table 4>,
          __tostring = <function 1>,
          initialize = <function 17>,
          isInstanceOf = <function 4>
        },
        name = "Tile",
        static = <5>{
          allocate = <function 7>,
          include = <function 8>,
          isSubclassOf = <function 9>,
          new = <function 10>,
          subclass = <function 11>,
          subclassed = <function 12>,
          <metatable> = {
            __index = <function 18>
          }
        },
        subclasses = {
          <metatable> = {
            __mode = "k"
          }
        },
        <metatable> = {
          __call = <function 14>,
          __index = <table 5>,
          __newindex = <function 15>,
          __tostring = <function 16>
        }
      },
      id = 0,
      tid = 4,
      <metatable> = <table 4>
    },
    ...

This is very long and hard to read. However, with some edits to inspect.lua I was able to get this:

<TileMapLayer>{
  tiles = {
    ["0,0"] = <Tile>{
      id = 0,
      tid = 4
    },
    ["0,1"] = <Tile>{
      id = 1,
      tid = 4
    },
   ...
}

This is much easier to read and more useful for simple printf debugging. :)

Here's the diff:

@@ -163,7 +163,8 @@ local function getKeys(t)
 
    local keys, keysLen = {}, 0
    for k in rawpairs(t) do
-      if not isSequenceKey(k, seqLen) then
+      -- Elias Daler EDIT: don't print "class" table
+      if not isSequenceKey(k, seqLen) and k ~= "class" then
          keysLen = keysLen + 1
          keys[keysLen] = k
       end
@@ -273,6 +274,11 @@ function Inspector:putValue(v)
    elseif tv == 'table' and not self.ids[v] then
       local t = v
 
+      -- Elias Daler EDIT: print middleclass instance name
+      if type(t.class) == "table" then -- metaclass obj instatnce
+         puts(buf, "<" .. t.class.name .. ">")
+      end
+
       if t == inspect.KEY or t == inspect.METATABLE then
          puts(buf, tostring(t))
       elseif self.level >= self.depth then
@@ -305,13 +311,14 @@ function Inspector:putValue(v)
             end
          end
 
-         local mt = getmetatable(t)
+         -- Elias Daler EDIT: don't print metatables
+         --[[ local mt = getmetatable(t)
          if type(mt) == 'table' then
             if seqLen + keysLen > 0 then puts(buf, ',') end
             tabify(self)
             puts(buf, '<metatable> = ')
             self:putValue(mt)
-         end
+         end ]]--
 
          self.level = self.level - 1

Obviously, this functionality should be optional and configurable by options. But what do you think about it?
Maybe it's worth making some option like "middleclass_simple_print" or something?

License question

Hey,
I want to use your lib in a game mod, but the game prevents mods from requiring external lua files so I need to copy your lib into my mod file (of course including the URL, LICENSE etc.). Are you okay with that approach?

custom __metatable metafield that is not nil or table breaks inspecting

So, what subject says. I was using Penlight's tablex.readonly to make tables read only (obviously) and catch mutation attempts. Apparently tablex.readonly not only sets __newindex metafields but also sets __metatable to false. Setting __metatable metafield to anything other than nil will cause getmetatable to return what's in __metatable field. Which leads us to the problem in function processRecursive:

local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)

-- if getmetatable(processed) returns false, then mt also is set to false,
-- and setmetatable call fails
local mt  = processRecursive(process, getmetatable(processed)--[[, etc]])
setmetatable(processedCopy, mt) -- boom

my solution was a simple workaround:

local mt  = processRecursive(process, getmetatable(processed)--[[, etc]])
-- solution
if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field
setmetatable(processedCopy, mt) -- no boom

it works, but perhaps it would be better to write a proper support for such cases. Maybe?

Printing with metatable's "__tostring" if userdata has it?

Hello. First of all, I want to thank you for a wonderful lib that I've used for several years now.

I've got an idea. Sometimes it's convenient to add "__tostring" to userdata's metatable so that it's printable in Lua. For example, if I bind my C++ 2D Vector class, it then can be easily printed like this:

v = Vector2f.new(10, 20) -- create instance of C++ Vector
print(tostring(v)) -- prints "(10, 20)"

But when I print it with inspect, I get:
<userdata 1>

But what if inspect printed something like this if __tostring is present in userdata's metatable?

<userdata 1, tostring = "(10, 20)">

And instead of getting output like this:

{
  room = {
    areaName = "forest",
    size = <userdata 1>
  }
}

I'll be able to get this:

{
  room = {
    areaName = "forest",
    size = <userdata 1, tostring="(10,20)">
  }
}

... Which will greatly help me when debugging some tables.
Another possibility is to make inspect write tables like this:

{
  room = {
    areaName = "forest",
    size = <userdata 1> -- (10, 20)
  }
}

Maybe this can be turned on and off with options if you don't like this behavior to be present by default. I can make a PR with my implementation if you agree that this might be a good feature to have.

Typo in the examples, oops

in the example

assert(inspect({ f = print, ud = some_user_data, thread = a_thread} ) == [[{
  f = <function 1>,
  u = <userdata 1>,
  thread = <thread 1>
}]])

u = <userdata 1> should be ud = <userdata 1>

.\inspect.lua:235: attempt to compare number with nil

Full error:

luajit: .\inspect.lua:235: attempt to compare number with nil
stack traceback:
        .\inspect.lua:235: in function 'putTable'
        .\inspect.lua:292: in function 'putValue'
        .\inspect.lua:262: in function 'f'
        .\inspect.lua:197: in function 'down'
        .\inspect.lua:243: in function 'putTable'
        .\inspect.lua:292: in function 'putValue'
        .\inspect.lua:323: in function 'inspect'
        test.lua:35: in main chunk
        [C]: at 0x7ff64c532000

Code I used to produce this error:

local createShim
createShim = function(options)
  options = options or {}
  local shim = {}
  local shimMetaTable = {
    __call = options.callFunc or function() end,
    __index = function(t, k)
      local newShim = createShim(options)
      t[k] = newShim
      return newShim
    end
  }
  if options.isWeak then shimMetaTable.__mode = 'kv' end
  setmetatable(shim, shimMetaTable)
  return shim
end

local shim = createShim({ isWeak = true })

local function getRandomLetter()
    return string.char(math.random(97, 122))
end

local inspect = require('inspect')

while true do
  local reference = shim
  for i = 1, math.random(1, 50) do
    local keyString = ''
    for i = 1, math.random(1, 20) do
      keyString = keyString .. getRandomLetter()
    end
    reference = reference[keyString]
  end
  print(inspect(shim))
end

Fixed by putting a guard in line 235:

----
  elseif self.level >= self.depth then
    self:puts('{...}')
  else
    -- BEFORE: if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
    if self.tableAppearances[t] and self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end

    local nonSequentialKeys = getNonSequentialKeys(t)
    local length            = rawlen(t)
----

Error: attempt to compare number with nil

I'm trying to debug my Pandoc Lua filter and I get this error when I'm trying to inspect certain objects:

Error running filter filter.lua:
/home/doron/.luarocks/share/lua/5.3/inspect.lua:246: attempt to compare number with nil
stack traceback:
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:246: in method 'putTable'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:303: in method 'putValue'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:263: in local 'f'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:208: in method 'down'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:253: in method 'putTable'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:303: in method 'putValue'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:263: in local 'f'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:208: in method 'down'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:253: in method 'putTable'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:303: in method 'putValue'
	...
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:303: in method 'putValue'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:263: in local 'f'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:208: in method 'down'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:253: in method 'putTable'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:303: in method 'putValue'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:272: in local 'f'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:208: in method 'down'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:253: in method 'putTable'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:303: in method 'putValue'
	/home/doron/.luarocks/share/lua/5.3/inspect.lua:334: in function 'inspect.inspect'
	(...tail calls...)
	[string "filter.lua"]:5: in function 'Pandoc'

And the Lua filter is:

inspect = require('inspect')

function Pandoc(doc)
	log = io.open("filter.log", "w")
	log:write(inspect(doc))
	log:close()
end

EDIT: Documentation for Pandoc Lua filters - https://pandoc.org/lua-filters.html

romtables not treated as tables

I'm trying to use inspect.lua with eLua, which adds the "romtable" type (LUA_TROTABLE). These are read-only tables that (for the purpose of inspection, anyway) work just like regular tables.

type(someromtable) returns "romtable" for these.

I've tried adding comparisions with 'romtable' in all the places I could find where the return value of type() was being compared with 'table', but had no success.

Would it be possible to add romtables to inspect.lua?

Thanks!

PERF: countCycles doesn't respect options.depth

inspect can be slow with { depth = 1 } on large tables because countCycles still fully processes the whole table.

inspect.lua/inspect.lua

Lines 304 to 314 in 9c8a68d

local depth = options.depth or (math.huge)
local newline = options.newline or '\n'
local indent = options.indent or ' '
local process = options.process
if process then
root = processRecursive(process, root, {}, {})
end
local cycles = {}
countCycles(root, cycles)

cycles is only used to print the id before a table, but if the additional references aren't printed then the id isn't useful. Definitely doesn't seem worth the cost just to indicate that a value is referenced deeper inside the table. I had cases where inspect(t, { depth = 1 }) took 1000 ms but removing countCycles reduced it to 1 ms.

Non-repeated tables should not get a number

Table numbering is a useful feature if tables get repeated at least once. However, most tables are not. Nevertheless, their table numbers are printed out, cluttering the output.

It would be nice if only the repeated tables got a number.

– You know what this means, right?
– Yup, it means I need to parse tables twice. Once to "count the repetitions" and assign numbers to tables, and a second time to actually generate the output, using those numbers.
– Exactly. Are you up to it?
– Certainly. But let me finish up my i18n stuff first, and then I'll get back to you.
– You such a cool guy. Having conversations with yourself in your tracker and all.
– Thanks. You are not bad yourself.

"Array"-style display option for tables

Firstly, I'd like to thank Kikito for producing this module. It's incredibly convenient for debugging, and it's literally invaluable to me in many cases (with metatables, or testing in restricted environments).

Currently the module outputs tables in this format:

{ "1", "2", [5] = "5", [6] = "once I pcall(A_FISH, ALIVE)" }

My suggestion is an option to display that table like this:

{ "1", "2", nil, nil, "5", "once I pcall(A_FISH, ALIVE)" }

Maybe options.array conditional?

Beyond the slight minification, the user could remove the brackets from the inspected table string. This produces a valid unpacked "array" (an arguments list!). So in a roundabout way, this could be useful for test units etc. (anything that needs to turn a static table into an arguments list).

Here's an example of this working well in the Chrome browser console (I feel JS shouldn't allow this kind of behaviour, but that's another story ;D).
image

_version

_version key still holds "2.0.0" value

Error when inspecting table returned by luaposix.dir

When applying inspect to a table returned by the dir function of the luaposix package an error is thrown.

posix = require 'posix'
inspect( posix.dir( posix.getenv()[ "HOME" ] ) )

I'm not a hundred percent sure if this error is reproducible since it could depend on the content of my home folder. If necessary I will do a more thorough search.

Incompatible with luvit or the Discordia library

I'm not sure how to formulate this issue, because I don't know if I should be using this with luvit in general, but here I go.

I'm using the Discordia library and it seems doing something as simple as inspect(client) will give the following error:

inspect.lua:246: attempt to compare number with nil

(here's the erroring line)

client being a discordia.Client object here.

Idea: long & nested array-like tables

Maybe array-like tables may be on one line if they are short (small amount of values or small sum of values' length?) and on separate lines if they are longer? E.g.

{ 
  {
    "assign", 
    { "id", "fun" },
    { "params", "a", "b", "c" }, 
    { 
      "block", 
      { "assign", 
        { "id", "A" }, 
        { "id", "2" } 
      }, 
      { "id", "4" }
    } 
  } 
}

instead of

{ { "assign", { "id", "fun" }, { "params", "a", "b", "c" }, { "block", { "assign", { "id", "A" }, { "id", "2" } }, { "id", "4" } } } }

Option to turn off metatable output

I often find it unnecessary to include metatables in debug output. E.g. I want to inspect the own state of a class instance (defined via classlib or similar library) and don't care about all the "inherited" methods.

Having an option (perhaps as 3rd parameter to inspect() function) to exclude metatables would really unclutter debug output.

Add unrolling visited tables

Hi there, awesome piece of soft!
I suggest to add an option to unroll already visited tables in the output. I.e.

local x = {1,2}
print( inspect( {x,x}, {unroll = true} )) -- should print {{1, 2}, {1, 2}}

I needed this functionality so I just commented out 3 lines:

241,242c241,242
<   elseif self:alreadyVisited(t) then
<     self:puts('<table ', self:getId(t), '>')
---
> --  elseif self:alreadyVisited(t) then
> --    self:puts('<table ', self:getId(t), '>')
246c246
< 		if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
---
> --		if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end

Control characters in strings are not escaped or incorrectly escaped

$ lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> inspect = require 'inspect'
> return inspect("\0")
"\000"  -- Okay
> return inspect("\\")
"\\"    -- Okay
> return inspect("\"")
'"'     -- Okay
> return inspect("\"'")
"\"'"   -- Okay
> return string.len(inspect("\006"))
3      -- Arguably wrong: shouldn't non-printing characters be escaped?
> return inspect("\t")
"\\t"   -- Wrong: doubly escaped
> return inspect("\n")
"\\n"   -- Wrong: doubly escaped

inspect a table lost some data

I use inspect print a table,but some data lost. for example.

inspect = require("inspect")
cjson = require("cjson")
tb = {[2]=28,[3]=1,[4]=90,sid=99,[13]=2,[26]=1,iid=1,[25]=1006}
--use pairs to print
for k, v in pairs(tb) do
    print(k,v)
end
--result:
--2       28
--3       1
--4       90
--sid     99
--13      2
--26      1
--iid     1
--25      1006
--use inspect
print(inspect(tb))
--result:
--{ nil, 28, 1, 90,
--  [13] = 2,
--  [25] = 1006,
--  [26] = 1,
--  iid = 1,
--  sid = 99
--}
--use cjson to print
print(csjon.encode(tb))
--result:
--{"2":28,"3":1,"4":90,"sid":99,"13":2,"26":1,"iid":1,"25":1006}
--use cjson encode,decode will be fine
print(inspect(cjson.decode(cjson.encode(tb))))
--result:
--{
--     ["13"] = 2,
--     ["2"] = 28,
--     ["25"] = 1006,
--     ["26"] = 1,
--     ["3"] = 1,
--     ["4"] = 90,
--     iid = 1,
--     sid = 99
--}

Any idea to solve data lost?

Odd results when inspecting table with a "1" index

Hey kikito, first of all let me say that I would not be programming Lua if it weren't for the inspect module. How they missed something like this in the core language beats me and without it I would have given up long ago. Thanks!

Having said that, today I noticed something odd. I was expecting the output of my tables to be the same when I inspect them but it seems that the minute I add an item in the "1" index, the output breaks somehow. If I don't add the "1" index, the output looks right.

local a = {}
a[1] = "one"
print(inspect(a))
-- { "one" }

a[1] = "one"
a[2] = "two"
print(inspect(a))
-- { "one", "two" }

local a = {}
a[0] = "zero"
a[1] = "one"
a[2] = "two"
print(inspect(a))
-- { "one", "two",
--   [0] = "zero"
-- }

local a = {}
a[0] = "zero"
a[2] = "two"
print(inspect(a))
-- {
--   [0] = "zero",
--   [2] = "two"
-- }

local a = {}
a[0] = "zero"
a[1] = "one"
a[2] = "two"
a[-55] = "minus fifty five"
print(inspect(a))
-- { "one", "two",
--   [-55] = "minus fifty five",
--   [0] = "zero"
-- }

local a = {}
a[0] = "zero"
a[2] = "two"
a[-55] = "minus fifty five"
print(inspect(a))
-- {
--   [-55] = "minus fifty five",
--   [0] = "zero",
--   [2] = "two"
-- }

No option to print lua table in one line

Hi, Thanks for providing such a wonderful library.

I have created a custom plugin on kong in lua. I am using inspect library to log the whole request received from the user in the error log file. These logs are then sent to an elastic data source.
Every log line of my application contains the metadata like nginx worker's pid, connection serial number, file name. But as we know that in this case, the table params are printed in separate lines, so a new log line is generated for a new param.

For example, if my request is - {a=1, b=2,c=3}, then the request logged in the error file is -
{
a=1,
b=2,
c=3
}

So I have no metadata information available for the params that are logged in new lines. This makes it difficult for me to get logs for the complete flow.

Is there a way I can get the complete request in one log line in this format - {a=1, b=2,c=3} ?

Lua crashes when inspecting _G

Environment

  • Windows 10 x64
  • Visual Studio 2022 14.34.31937 toolchain
  • Lua 5.4.4 + LuaBridge

The Crash
When inspecting _G it seems like the metamethod __eq is called on the ExampleClass.
But apparently there is no valid userdata passed resulting in a crash.

class ExampleClass
{
public:
	ExampleClass() :
		a(0), b(0), c(0)
	{

	}

	string tostring() const
	{
		return "whatever";
	}

	bool operator==(const ExampleClass& v) const
	{
		return a == v.a && b == v.b && c == v.c;
	}

	int a, b, c;
};

lua_State* L = luaL_newstate();

luaL_openlibs(L);

luabridge::getGlobalNamespace(L)
	.beginClass<ExampleClass>("ExampleClass")
		.addConstructor<void(*) ()>()
		.addFunction("tostring", &ExampleClass::tostring)
		.addFunction("__tostring", &ExampleClass::tostring)
		.addFunction("__eq", &ExampleClass::operator==)
	.endClass()
;

luaL_dostring(L, "inspect = require('inspect'); print(inspect(_G));");
main.exe!luabridge::detail::Userdata::getPointer() Line 46	C++
main.exe!luabridge::detail::Userdata::get<ExampleClass>(lua_State * L, int index, bool canBeConst) Line 254	C++
main.exe!luabridge::detail::CFunc::CallConstMember<bool (__cdecl ExampleClass::*)(ExampleClass const &)const>::f(lua_State * L) Line 291	C++
main.exe!precallC(lua_State * L, StackValue * func, int nresults, int(*)(lua_State *) f) Line 506	C
main.exe!luaD_precall(lua_State * L, StackValue * func, int nresults) Line 570	C
main.exe!ccall(lua_State * L, StackValue * func, int nResults, int inc) Line 607	C
main.exe!luaD_call(lua_State * L, StackValue * func, int nResults) Line 620	C
main.exe!luaT_callTMres(lua_State * L, const TValue * f, const TValue * p1, const TValue * p2, StackValue * res) Line 129	C
main.exe!luaV_equalobj(lua_State * L, const TValue * t1, const TValue * t2) Line 612	C
main.exe!luaV_execute(lua_State * L, CallInfo * ci) Line 1566	C
main.exe!ccall(lua_State * L, StackValue * func, int nResults, int inc) Line 611	C
main.exe!luaD_callnoyield(lua_State * L, StackValue * func, int nResults) Line 628	C
main.exe!f_call(lua_State * L, void * ud) Line 1042	C
main.exe!luaD_rawrunprotected(lua_State * L, void(*)(lua_State *, void *) f, void * ud) Line 147	C
main.exe!luaD_pcall(lua_State * L, void(*)(lua_State *, void *) func, void * u, __int64 old_top, __int64 ef) Line 926	C
main.exe!lua_pcallk(lua_State * L, int nargs, int nresults, int errfunc, __int64 ctx, int(*)(lua_State *, int, __int64) k) Line 1067	C
main.exe!main() Line 99	C++

Using inspect on table with __metatable of the same table, causes stack overflow and gettable loop

If you have a table 'T', with a metatable with '__metatable' = T, then inspect.lua causes either a stack overflow or loop in gettable.
That scenario may sound odd, but I've seen some C-bindings do it that way.

The first problem is Inspector:countTableAppearances() walks through all tables, and calls getmetatable() and loops in that - since getmetatable() will return the same table, it will cause a stack overflow looping forever. To fix it, change the function to have a second argument:

function Inspector:countTableAppearances(t, isMeta)

and change this:

self:countTableAppearances(getmetatable(t))

to this:

if not isMeta then self:countTableAppearances(getmetatable(t), true) end

The second problem is inspect.lua's getToStringResultSafely() function tries to get the metatable's '__tostring' member, but really the metatable is in fact the original table, because getmetatable(T) called in Inspector:putTable(t) will return what '__metatable' is, rather than the real metatable - that's the point of the '__metatable' action, to hide the real metatable from Lua code. So when getToStringResultSafely() tries to fetch the mt.__tostring, it invokes the real metatable's '__index' action, since '__tostring' is not a member of T. The Lua VM detects that as a loop. So... use rawget() instead, by changing this:

local __tostring = type(mt) == 'table' and mt.__tostring

to this:

local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')

Option to filter keys not values

Hi,
This isn't really an "issue", but more of a courtesy message. :)

I'm adding inspect into Wireshark's test suite, to verify Wireshark's Lua global table didn't remove things from previous versions, and then output what was newly added to make sure it's what was expected. Since some of the things in the global table change every release or from machine-to-machine (like paths and such), I was going to use inspect's filter option to filter them out. But obviously inspect filters matching values, not keys.[1] In other words, if I filter {foobar}, anything with foobar's value is filtered. Instead, I want to specify the specific table key I want to filter. So I changed inspect to also have a "keyfilter" option, which takes a table of key names and filters based on that as well. For example a keyfilter of { "_VERSION", "package.cpath", "package.path", "package.loaded.inspect" } would filter those keys from inspect's output, in a similar way that the existing value filter does. (and you can use both options at the same time of course)

If you would like to get the changes I made, let me know.

[1] well... inspect currently filters matching keys too in some cases: if the key isn't an identifier, because putKey() calls putValue() which filters.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.