gists/pt.lua

186 lines
4.6 KiB
Lua
Raw Normal View History

2016-12-11 04:02:36 -08:00
local path = string.gsub(..., "[^.]+$", "")
local extra = require(path.."extra")
2016-12-11 03:54:59 -08:00
local opairs = extra.opairs
2014-08-20 17:44:41 -07:00
local pt = {}
pt.__index = pt
setmetatable(pt, pt)
2016-12-11 03:54:59 -08:00
local function rawstr(v)
2014-08-20 17:44:41 -07:00
if v == nil then return 'nil' end
local mt = getmetatable(v)
local ts = mt and rawget(mt, '__tostring')
if not ts then return tostring(v) end
mt.__tostring = nil
local s = tostring(v)
mt.__tostring = ts
return s
end
2016-12-11 03:54:59 -08:00
local function getaddr(t)
2014-08-20 17:44:41 -07:00
return rawstr(t):sub(#type(t) + 3)
end
2016-12-11 03:54:59 -08:00
local function copy(t)
2015-11-21 20:01:40 -08:00
-- shallow copy
if type(t) ~= 'table' then return end
local new = {}
2016-12-11 04:56:57 -08:00
for k,v in pairs(t) do
new[k] = v
2015-11-21 20:01:40 -08:00
end
return new
end
function pt.__call(pt, args)
2014-08-20 17:44:41 -07:00
-- print a table as semi-valid YAML
-- with references to prevent recursion/duplication
2015-11-21 20:01:40 -08:00
local t = args.table or args[1]
2014-08-20 17:44:41 -07:00
local self = {}
setmetatable(self, pt)
2015-11-21 20:01:40 -08:00
self.seen = copy(args.seen) or {}
self.skipped = copy(args.skipped) or {}
self.seen_elsewhere = args.seen or {}
self.depth = args.depth or 16
self.writer = args.writer or io.write
self.skeleton = args.skeleton or false
2016-12-11 04:56:57 -08:00
self.outer = args.alt_order and self.outer_old or self.outer
self.indent = args.indent or ' '
2014-08-20 17:44:41 -07:00
self.queued = {}
2016-12-11 04:56:57 -08:00
self.cache = {}
2014-08-20 17:44:41 -07:00
self:inner('__root__', t, '')
return self.seen
end
function pt:write(...)
self.writer(...)
end
function pt.safekey(k)
if type(k) == 'table' then
return 't'..getaddr(k)
end
local s = tostring(k)
s = s:gsub('[\r\n]', '')
return s:find('[^%w_]') and ('%q'):format(s) or s
end
2016-12-11 04:56:57 -08:00
function pt.safeval(v, indentation, indent)
2014-08-20 17:44:41 -07:00
if type(v) == 'function' then
return 'f'..getaddr(v)
end
local s = tostring(v)
if type(v) == 'number' then
return s
end
2016-12-11 04:56:57 -08:00
-- TODO: move newline/indentation handling to another function?
if s:find('[\r\n]') then
s = ('\n'..s):gsub('[\r\n]', '\n'..indentation..indent)
end
2015-11-21 20:01:40 -08:00
--local safe = ('%q'):format(s)
--return s == safe:sub(2, -2) and s or safe
-- TODO: finish matching valid characters
return s:find('[^%w_()[]{}.]') and ('%q'):format(s) or s
2014-08-20 17:44:41 -07:00
end
2016-12-11 04:56:57 -08:00
function pt:inner(k, v, indentation)
2014-08-20 17:44:41 -07:00
if type(v) ~= 'table' then
2015-11-21 20:01:40 -08:00
if self.skeleton then return end
2016-12-11 04:56:57 -08:00
self:write(indentation, pt.safekey(k), ': ')
self:write(pt.safeval(v, indentation, self.indent), '\n')
2014-08-20 17:44:41 -07:00
return
end
local addr = getaddr(v)
2016-12-11 04:56:57 -08:00
self:write(indentation, pt.safekey(k))
2015-11-21 20:01:40 -08:00
2016-12-11 04:56:57 -08:00
if #indentation > self.depth or self.skipped[addr] then
2015-11-21 20:01:40 -08:00
--self.skipped[addr] = true -- TODO: extra logics
self:write(': #t', addr, '\n')
return
end
2014-08-20 17:44:41 -07:00
if self.seen[addr] or self.queued[addr] then
2015-11-21 20:01:40 -08:00
self:write(': *t', addr, self.seen_elsewhere[addr] and ' #\n' or '\n')
2014-08-20 17:44:41 -07:00
return
end
self.seen[addr] = true
self:write(': &t', addr, '\n')
2016-12-11 04:56:57 -08:00
self:outer(v, indentation..self.indent)
2014-08-20 17:44:41 -07:00
end
2016-12-11 04:56:57 -08:00
function pt:outer_old(t, indentation)
2014-08-20 17:44:41 -07:00
if type(t) ~= "table" then
2016-12-11 04:56:57 -08:00
local s = pt.safeval(t, indentation, self.indent)
self:write(indentation, s, '\n')
2014-08-20 17:44:41 -07:00
return
end
local ours = {}
local not_ours = {}
for k,v in opairs(t) do
if type(v) == 'table' then
local addr = getaddr(v)
2016-12-11 04:56:57 -08:00
if not (self.queued[addr] or self.seen[addr] or self.skipped[addr]) then
2014-08-20 17:44:41 -07:00
self.queued[addr] = true
ours[k] = v
else
not_ours[k] = v
end
else
2016-12-11 04:56:57 -08:00
self:inner(k, v, indentation)
2014-08-20 17:44:41 -07:00
end
end
for k,v in opairs(not_ours) do
2016-12-11 04:56:57 -08:00
self:inner(k, v, indentation)
2014-08-20 17:44:41 -07:00
end
for k,v in opairs(ours) do
self.queued[getaddr(v)] = nil
2016-12-11 04:56:57 -08:00
self:inner(k, v, indentation)
end
local mt = getmetatable(t)
if mt ~= nil then
self:inner('__metatable', mt, indentation)
end
end
function pt:outer(t, indentation)
if type(t) ~= "table" then
local s = pt.safeval(t, indentation, self.indent)
self:write(indentation, s, '\n')
return
end
local ours = {}
for k,v in opairs(t, self.cache) do
if type(v) == 'table' then
local addr = getaddr(v)
if not (self.queued[addr] or self.seen[addr] or self.skipped[addr]) then
self.queued[addr] = true
ours[k] = addr
end
end
2014-08-20 17:44:41 -07:00
end
local mt = getmetatable(t)
if mt ~= nil then
2016-12-11 04:56:57 -08:00
self:inner('__metatable', mt, indentation)
end
for k,v in opairs(t, self.cache) do
local addr = ours[k]
if addr then
self.queued[addr] = nil
end
self:inner(k, v, indentation)
2014-08-20 17:44:41 -07:00
end
end
return pt