pretty.lua
local append = table.insert
local concat = table.concat
local utils = require 'pl.utils'
local lexer = require 'pl.lexer'
local assert_arg = utils.assert_arg
local pretty = {}
local function save_string_index ()
local SMT = getmetatable ''
if SMT then
SMT.old__index = SMT.__index
SMT.__index = nil
end
return SMT
end
local function restore_string_index (SMT)
if SMT then
SMT.__index = SMT.old__index
end
end
function pretty.read(s)
assert_arg(1,s,'string')
if s:find '^%s*%-%-' then s = s:gsub('%-%-.-\n','')
end
if not s:find '^%s*%b{}%s*$' then return nil,"not a Lua table" end
if s:find '[^\'"%w_]function[^\'"%w_]' then
local tok = lexer.lua(s)
for t,v in tok do
if t == 'keyword' then
return nil,"cannot have functions in table definition"
end
end
end
s = 'return '..s
local chunk,err = utils.load(s,'tbl','t',{})
if not chunk then return nil,err end
local SMT = save_string_index()
local ok,ret = pcall(chunk)
restore_string_index(SMT)
if ok then return ret
else
return nil,ret
end
end
function pretty.load (s, env, paranoid)
env = env or {}
if paranoid then
local tok = lexer.lua(s)
for t,v in tok do
if t == 'keyword'
and (v == 'for' or v == 'repeat' or v == 'function' or v == 'goto')
then
return nil,"looping not allowed"
end
end
end
local chunk,err = utils.load(s,'tbl','t',env)
if not chunk then return nil,err end
local SMT = paranoid and save_string_index()
local ok,err = pcall(chunk)
restore_string_index(SMT)
if not ok then return nil,err end
return env
end
local function quote_if_necessary (v)
if not v then return ''
else
if v:find ' ' then v = '"'..v..'"' end
end
return v
end
local keywords
local function is_identifier (s)
return type(s) == 'string' and s:find('^[%a_][%w_]*$') and not keywords[s]
end
local function quote (s)
if type(s) == 'table' then
return pretty.write(s,'')
else
return ('%q'):format(tostring(s))
end
end
local function index (numkey,key)
if not numkey then key = quote(key) end
return '['..key..']'
end
function pretty.write (tbl,space,not_clever)
if type(tbl) ~= 'table' then
local res = tostring(tbl)
if type(tbl) == 'string' then return quote(tbl) end
return res, 'not a table'
end
if not keywords then
keywords = lexer.get_keywords()
end
local set = ' = '
if space == '' then set = '=' end
space = space or ' '
local lines = {}
local line = ''
local tables = {}
local function put(s)
if #s > 0 then
line = line..s
end
end
local function putln (s)
if #line > 0 then
line = line..s
append(lines,line)
line = ''
else
append(lines,s)
end
end
local function eat_last_comma ()
local n,lastch = #lines
local lastch = lines[n]:sub(-1,-1)
if lastch == ',' then
lines[n] = lines[n]:sub(1,-2)
end
end
local writeit
writeit = function (t,oldindent,indent)
local tp = type(t)
if tp ~= 'string' and tp ~= 'table' then
putln(quote_if_necessary(tostring(t))..',')
elseif tp == 'string' then
if t:find('\n') then
putln('[[\n'..t..']],')
else
putln(quote(t)..',')
end
elseif tp == 'table' then
if tables[t] then
putln('<cycle>,')
return
end
tables[t] = true
local newindent = indent..space
putln('{')
local used = {}
if not not_clever then
for i,val in ipairs(t) do
put(indent)
writeit(val,indent,newindent)
used[i] = true
end
end
for key,val in pairs(t) do
local numkey = type(key) == 'number'
if not_clever then
key = tostring(key)
put(indent..index(numkey,key)..set)
writeit(val,indent,newindent)
else
if not numkey or not used[key] then if numkey or not is_identifier(key) then
key = index(numkey,key)
end
put(indent..key..set)
writeit(val,indent,newindent)
end
end
end
tables[t] = nil
eat_last_comma()
putln(oldindent..'},')
else
putln(tostring(t)..',')
end
end
writeit(tbl,'',space)
eat_last_comma()
return concat(lines,#space > 0 and '\n' or '')
end
function pretty.dump (t,...)
if select('#',...)==0 then
print(pretty.write(t))
return true
else
return utils.writefile(...,pretty.write(t))
end
end
local memp,nump = {'B','KiB','MiB','GiB'},{'','K','M','B'}
local comma
function comma (val)
local thou = math.floor(val/1000)
if thou > 0 then return comma(thou)..','..(val % 1000)
else return tostring(val) end
end
function pretty.number (num,kind,prec)
local fmt = '%.'..(prec or 1)..'f%s'
if kind == 'T' then
return comma(num)
else
local postfixes, fact
if kind == 'M' then
fact = 1024
postfixes = memp
else
fact = 1000
postfixes = nump
end
local div = fact
local k = 1
while num >= div and k <= #postfixes do
div = div * fact
k = k + 1
end
div = div / fact
if k > #postfixes then k = k - 1; div = div/fact end
if k > 1 then
return fmt:format(num/div,postfixes[k] or 'duh')
else
return num..postfixes[1]
end
end
end
return pretty