Add options.override, refactor

This commit is contained in:
Enrique García Cota 2022-05-21 01:24:44 +02:00
parent 78c67816ef
commit dc5a7f48d2
3 changed files with 134 additions and 137 deletions

View File

@ -13,7 +13,6 @@ local inspect = {Options = {}, }
inspect._VERSION = 'inspect.lua 3.1.0' inspect._VERSION = 'inspect.lua 3.1.0'
inspect._URL = 'http://github.com/kikito/inspect.lua' inspect._URL = 'http://github.com/kikito/inspect.lua'
inspect._DESCRIPTION = 'human-readable representations of tables' inspect._DESCRIPTION = 'human-readable representations of tables'
@ -41,8 +40,6 @@ inspect._LICENSE = [[
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]] ]]
inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end })
inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end })
local tostring = tostring local tostring = tostring
local rep = string.rep local rep = string.rep
@ -50,6 +47,7 @@ local match = string.match
local char = string.char local char = string.char
local gsub = string.gsub local gsub = string.gsub
local fmt = string.format local fmt = string.format
local concat = table.concat
local function rawpairs(t) local function rawpairs(t)
return next, t, nil return next, t, nil
@ -150,33 +148,8 @@ local function countCycles(x, cycles)
end end
end end
local function puts(buf, str) local function getId(ids, v)
buf.n = buf.n + 1 local id = ids[v]
buf[buf.n] = str
end
local Inspector = {}
local Inspector_mt = { __index = Inspector }
local function tabify(inspector)
puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level))
end
function Inspector:getId(v)
local id = self.ids[v]
local ids = self.ids
if not id then if not id then
local tv = type(v) local tv = type(v)
id = (ids[tv] or 0) + 1 id = (ids[tv] or 0) + 1
@ -185,61 +158,77 @@ function Inspector:getId(v)
return tostring(id) return tostring(id)
end end
function Inspector:putValue(v) local function puts(buf, str)
local buf = self.buf buf.n = buf.n + 1
buf[buf.n] = str
end
local function tabify(buf, level, options)
local newline = options and options.newline or "\n"
local indent = options and options.indent or " "
puts(buf, newline .. rep(indent, level))
end
local function putValue(v, buf, cycles, ids, level,
options)
if options and options.override then
local s = options.override(v)
if s ~= nil then
puts(buf, s)
return
end
end
local tv = type(v) local tv = type(v)
if tv == 'string' then if tv == 'string' then
puts(buf, smartQuote(escape(v))) puts(buf, smartQuote(escape(v)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
tv == 'cdata' or tv == 'ctype' then tv == 'cdata' or tv == 'ctype' then
puts(buf, tostring(v)) puts(buf, tostring(v))
elseif tv == 'table' and not self.ids[v] then elseif tv == 'table' and not ids[v] then
local t = v local t = v
if t == inspect.KEY or t == inspect.METATABLE then local depth = options and options.depth
puts(buf, tostring(t)) if depth and level >= depth then
elseif self.level >= self.depth then
puts(buf, '{...}') puts(buf, '{...}')
else else
if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end if cycles[t] > 1 then puts(buf, fmt('<%d>', getId(ids, t))) end
local keys, keysLen, seqLen = getKeys(t) local keys, keysLen, seqLen = getKeys(t)
local nextLevel = level + 1
puts(buf, '{') puts(buf, '{')
self.level = self.level + 1
for i = 1, seqLen + keysLen do for i = 1, seqLen + keysLen do
if i > 1 then puts(buf, ',') end if i > 1 then puts(buf, ',') end
if i <= seqLen then if i <= seqLen then
puts(buf, ' ') puts(buf, ' ')
self:putValue(t[i]) putValue(t[i], buf, cycles, ids, nextLevel, options)
else else
local k = keys[i - seqLen] local k = keys[i - seqLen]
tabify(self) tabify(buf, level + 1, options)
if isIdentifier(k) then if isIdentifier(k) then
puts(buf, k) puts(buf, k)
else else
puts(buf, "[") puts(buf, "[")
self:putValue(k) putValue(k, buf, cycles, ids, nextLevel, options)
puts(buf, "]") puts(buf, "]")
end end
puts(buf, ' = ') puts(buf, ' = ')
self:putValue(t[k]) putValue(t[k], buf, cycles, ids, nextLevel, options)
end end
end end
local mt = getmetatable(t) local mt = getmetatable(t)
if type(mt) == 'table' then if type(mt) == 'table' then
if seqLen + keysLen > 0 then puts(buf, ',') end if seqLen + keysLen > 0 then puts(buf, ',') end
tabify(self) tabify(buf, level + 1, options)
puts(buf, '<metatable> = ') puts(buf, '<metatable> = ')
self:putValue(mt) putValue(mt, buf, cycles, ids, nextLevel, options)
end end
self.level = self.level - 1
if keysLen > 0 or type(mt) == 'table' then if keysLen > 0 or type(mt) == 'table' then
tabify(self) tabify(buf, level, options)
elseif seqLen > 0 then elseif seqLen > 0 then
puts(buf, ' ') puts(buf, ' ')
end end
@ -248,7 +237,7 @@ function Inspector:putValue(v)
end end
else else
puts(buf, fmt('<%s %d>', tv, self:getId(v))) puts(buf, fmt('<%s %d>', tv, getId(ids, v)))
end end
end end
@ -256,28 +245,13 @@ end
function inspect.inspect(root, options) function inspect.inspect(root, options)
options = options or {}
local depth = options.depth or (math.huge)
local newline = options.newline or '\n'
local indent = options.indent or ' '
local cycles = {} local cycles = {}
countCycles(root, cycles) countCycles(root, cycles)
local inspector = setmetatable({ local buf = { n = 0 }
buf = { n = 0 }, putValue(root, buf, cycles, {}, 0, options)
ids = {},
cycles = cycles,
depth = depth,
level = 0,
newline = newline,
indent = indent,
}, Inspector_mt)
inspector:putValue(root) return concat(buf)
return table.concat(inspector.buf)
end end
setmetatable(inspect, { setmetatable(inspect, {

View File

@ -4,13 +4,12 @@ local record inspect
_URL: string _URL: string
_DESCRIPTION: string _DESCRIPTION: string
_LICENSE: string _LICENSE: string
KEY: table
METATABLE: table
record Options record Options
depth: integer depth: integer
newline: string newline: string
indent: string indent: string
override: function(any): string
end end
end end
@ -41,8 +40,6 @@ inspect._LICENSE = [[
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]] ]]
inspect.KEY = setmetatable({}, {__tostring = function(): string return 'inspect.KEY' end})
inspect.METATABLE = setmetatable({}, {__tostring = function(): string return 'inspect.METATABLE' end})
local tostring = tostring local tostring = tostring
local rep = string.rep local rep = string.rep
@ -50,6 +47,7 @@ local match = string.match
local char = string.char local char = string.char
local gsub = string.gsub local gsub = string.gsub
local fmt = string.format local fmt = string.format
local concat = table.concat
local function rawpairs(t: table): function, table, nil local function rawpairs(t: table): function, table, nil
return next, t, nil return next, t, nil
@ -135,7 +133,7 @@ local function getKeys(t: table): {any}, integer, integer
return keys, keysLen, seqLen return keys, keysLen, seqLen
end end
local function countCycles(x: any, cycles: {any:integer}): nil local function countCycles(x: any, cycles: {table: integer}): nil
if x is table then if x is table then
if cycles[x] then if cycles[x] then
cycles[x] = cycles[x] + 1 cycles[x] = cycles[x] + 1
@ -150,33 +148,8 @@ local function countCycles(x: any, cycles: {any:integer}): nil
end end
end end
local function puts(buf: table, str:string): nil local function getId(ids: {any:integer}, v:any): string
buf.n = buf.n as integer + 1 local id: integer = ids[v]
buf[buf.n as integer] = str
end
-------------------------------------------------------------------
local type Inspector = record
buf: table
depth: integer
level: integer
ids: {any:integer}
newline: string
indent: string
cycles: {table: integer}
puts: function(string)
end
local Inspector_mt = {__index = Inspector}
local function tabify(inspector: Inspector)
puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level))
end
function Inspector:getId(v: any): string
local id: integer = self.ids[v]
local ids = self.ids
if not id then if not id then
local tv: string = type(v) local tv: string = type(v)
id = (ids[tv] or 0) + 1 id = (ids[tv] or 0) + 1
@ -185,61 +158,77 @@ function Inspector:getId(v: any): string
return tostring(id) return tostring(id)
end end
function Inspector:putValue(v: any) local function puts(buf: table, str:string): nil
local buf = self.buf buf.n = buf.n as integer + 1
buf[buf.n as integer] = str
end
local function tabify(buf: table, level: integer, options: inspect.Options)
local newline = options and options.newline or "\n"
local indent = options and options.indent or " "
puts(buf, newline .. rep(indent, level))
end
local function putValue(v: any, buf: table, cycles: {table: integer}, ids: {any: integer}, level: integer,
options: inspect.Options)
if options and options.override then
local s = options.override(v)
if s ~= nil then
puts(buf, s)
return
end
end
local tv: string = type(v) local tv: string = type(v)
if tv == 'string' then if tv == 'string' then
puts(buf, smartQuote(escape(v as string))) puts(buf, smartQuote(escape(v as string)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
tv == 'cdata' or tv == 'ctype' then tv == 'cdata' or tv == 'ctype' then
puts(buf, tostring(v as number)) puts(buf, tostring(v as number))
elseif tv == 'table' and not self.ids[v] then elseif tv == 'table' and not ids[v] then
local t = v as table local t = v as table
if t == inspect.KEY or t == inspect.METATABLE then local depth = options and options.depth
puts(buf, tostring(t)) if depth and level >= depth then
elseif self.level >= self.depth then
puts(buf, '{...}') puts(buf, '{...}')
else else
if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end if cycles[t] > 1 then puts(buf, fmt('<%d>', getId(ids, t))) end
local keys, keysLen, seqLen = getKeys(t) local keys, keysLen, seqLen = getKeys(t)
local nextLevel = level + 1
puts(buf, '{') puts(buf, '{')
self.level = self.level + 1
for i = 1, seqLen + keysLen do for i = 1, seqLen + keysLen do
if i > 1 then puts(buf, ',') end if i > 1 then puts(buf, ',') end
if i <= seqLen then if i <= seqLen then
puts(buf, ' ') puts(buf, ' ')
self:putValue(t[i]) putValue(t[i], buf, cycles, ids, nextLevel, options)
else else
local k = keys[i - seqLen] local k = keys[i - seqLen]
tabify(self) tabify(buf, level + 1, options)
if isIdentifier(k) then if isIdentifier(k) then
puts(buf, k as string) puts(buf, k as string)
else else
puts(buf, "[") puts(buf, "[")
self:putValue(k) putValue(k, buf, cycles, ids, nextLevel, options)
puts(buf, "]") puts(buf, "]")
end end
puts(buf, ' = ') puts(buf, ' = ')
self:putValue(t[k]) putValue(t[k], buf, cycles, ids, nextLevel, options)
end end
end end
local mt = getmetatable(t) local mt = getmetatable(t)
if type(mt) == 'table' then if type(mt) == 'table' then
if seqLen + keysLen > 0 then puts(buf, ',') end if seqLen + keysLen > 0 then puts(buf, ',') end
tabify(self) tabify(buf, level + 1, options)
puts(buf, '<metatable> = ') puts(buf, '<metatable> = ')
self:putValue(mt) putValue(mt, buf, cycles, ids, nextLevel, options)
end end
self.level = self.level - 1
if keysLen > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing } if keysLen > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing }
tabify(self) tabify(buf, level, options)
elseif seqLen > 0 then -- array tables have one extra space before closing } elseif seqLen > 0 then -- array tables have one extra space before closing }
puts(buf, ' ') puts(buf, ' ')
end end
@ -248,7 +237,7 @@ function Inspector:putValue(v: any)
end end
else else
puts(buf, fmt('<%s %d>', tv, self:getId(v))) puts(buf, fmt('<%s %d>', tv, getId(ids, v)))
end end
end end
@ -256,28 +245,13 @@ end
function inspect.inspect(root: any, options: inspect.Options): string function inspect.inspect(root: any, options: inspect.Options): string
options = options or {} local cycles: {table: integer} = {}
local depth: integer = options.depth or (math.huge as integer)
local newline: string = options.newline or '\n'
local indent: string = options.indent or ' '
local cycles = {}
countCycles(root, cycles) countCycles(root, cycles)
local inspector = setmetatable({ local buf: table = { n = 0 }
buf = { n = 0 }, putValue(root, buf, cycles, {}, 0, options)
ids = {},
cycles = cycles,
depth = depth,
level = 0,
newline = newline,
indent = indent,
} as Inspector, Inspector_mt)
inspector:putValue(root) return concat(buf as {string})
return table.concat(inspector.buf as {string})
end end
setmetatable(inspect, { setmetatable(inspect, {

View File

@ -262,6 +262,55 @@ describe( 'inspect', function()
end) end)
end) end)
describe('the override option', function()
it('changes how long strings are rendered, leaves values unaltered on nil', function()
local t = {short = "short", long = "a long string that should be shortened"}
assert.equal(unindent([[
{
long = "shortened",
short = "short"
}
]]), inspect(t, {override = function(x)
if type(x) == "string" and #x > 5 then
return '"shortened"'
end
end}))
end)
it('overrides hash values', function()
local t = { b = 2, c = "a" }
assert.equal(unindent([[
{
b = 2,
c = "changed"
}
]]), inspect(t, {override = function(x)
if x == "a" then
return '"changed"'
end
end}))
end)
it('can hide metatables', function()
local t = setmetatable({a = 1}, {__index = "a"})
assert.equal(unindent([[
{
a = 1,
<metatable> = {_}
}
]]), inspect(t, {override = function(x)
if type(x) == "table" and x.__index then
return "{_}"
end
end}))
end)
end)
describe('metatables', function() describe('metatables', function()
it('includes the metatable as an extra hash attribute', function() it('includes the metatable as an extra hash attribute', function()