Added load function to deserialize; updated documentation (closes #9).

This commit is contained in:
Paul Kulchenko 2013-11-05 11:52:05 -08:00
parent b290a5d522
commit 4e4a19df1d
4 changed files with 86 additions and 4 deletions

View File

@ -37,6 +37,10 @@ print(serpent.block(a)) -- multi-line indented, no self-ref section
local fun, err = loadstring(serpent.dump(a)) local fun, err = loadstring(serpent.dump(a))
if err then error(err) end if err then error(err) end
local copy = fun() local copy = fun()
-- or using serpent.load:
local ok, copy = serpent.load(serpent.dump(a))
print(ok and copy[3] == a[3])
``` ```
## Functions ## Functions
@ -51,6 +55,12 @@ internal function, but set different options by default:
Note that `line` and `block` functions return pretty-printed data structures and if you want to deserialize them, you need to add `return` before running them through `loadstring`. Note that `line` and `block` functions return pretty-printed data structures and if you want to deserialize them, you need to add `return` before running them through `loadstring`.
For example: `loadstring('return '..require('mobdebug').line("foo"))() == "foo"`. For example: `loadstring('return '..require('mobdebug').line("foo"))() == "foo"`.
While you can use `loadstring` or `load` functions to load serialized fragments, Serpent also provides `load` function that adds safety checks and reports an error if there is any executable code in the fragment.
* `ok, res = load(str[, {safe = true}])` -- loads serialized fragment; you need to pass `{safe = false}` as the second value if you want to turn safety checks off.
Similar to `pcall` and `loadstring` calls, `load` returns status as the first value and the result or the error message as the second value.
## Options ## Options
* indent (string) -- indentation; triggers long multi-line output * indent (string) -- indentation; triggers long multi-line output
@ -167,6 +177,10 @@ See LICENSE file.
## History ## History
### v0.26 (Nov 05 2013)
- Added `load` function that (safely) loads serialized/pretty-printed values.
- Updated documentation.
### v0.25 (Sep 29 2013) ### v0.25 (Sep 29 2013)
- Added `maxnum` option to limit the number of elements in tables. - Added `maxnum` option to limit the number of elements in tables.
- Optimized processing of tables with numeric indexes. - Optimized processing of tables with numeric indexes.

View File

@ -0,0 +1,25 @@
package = "serpent"
version = "0.26-1"
source = {
url = "git://github.com/pkulchenko/serpent.git",
tag = "0.26"
}
description = {
summary = "Lua serializer and pretty printer",
homepage = "https://github.com/pkulchenko/serpent",
maintainer = "Paul Kulchenko <paul@kulchenko.com>",
license = "MIT",
}
dependencies = {
"lua >= 5.1, < 5.3",
}
build = {
type = "builtin",
modules = {
["serpent"] = "src/serpent.lua",
},
copy_directories = { "t" },
}

View File

@ -1,4 +1,4 @@
local n, v = "serpent", 0.25 -- (C) 2012-13 Paul Kulchenko; MIT License local n, v = "serpent", 0.26 -- (C) 2012-13 Paul Kulchenko; MIT License
local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
local badtype = {thread = true, userdata = true, cdata = true} local badtype = {thread = true, userdata = true, cdata = true}
@ -103,8 +103,26 @@ local function s(t, opts)
return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
end end
local function deserialize(data, opts)
local f, res = (loadstring or load)('return '..data)
if not f then f, res = (loadstring or load)(data) end
if not f then return f, res end
if opts and opts.safe == false then return pcall(f) end
local count, thread = 0, coroutine.running()
local h, m, c = debug.gethook(thread)
debug.sethook(function (e, l) count = count + 1
if count >= 3 then error("cannot call functions") end
end, "c")
local res = {pcall(f)}
count = 0 -- set again, otherwise it's tripped on the next sethook
debug.sethook(thread, h, m, c)
return (table.unpack or unpack)(res)
end
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
load = deserialize,
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }

View File

@ -261,8 +261,35 @@ do
assert(called == false, "sorting is not called on numeric-only tables with maxnum: failed") assert(called == false, "sorting is not called on numeric-only tables with maxnum: failed")
end end
-- test serializing large numeric-only tables
do do
local ok, res = serpent.load(serpent.line(10))
assert(ok and res == 10, "deserialization of simple number values: failed")
local ok, res = serpent.load(serpent.line(true))
assert(ok and res == true, "deserialization of simple boolean values: failed")
local ok, res = serpent.load(serpent.line({3,4}))
assert(ok and #res == 2 and res[1] == 3 and res[2] == 4,
"deserialization of pretty-printed tables: failed")
local ok, res = serpent.load(serpent.dump({3,4}))
assert(ok and #res == 2 and res[1] == 3 and res[2] == 4,
"deserialization of serialized tables: failed")
local ok, res = serpent.load('{a = math.random()}')
assert(not ok and res:find("cannot call functions"),
"deserialization of unsafe values: failed")
local ok, res = serpent.load('{a = math.random()}', {safe = false})
assert(ok and res and res.a > 0,
"deserialization of unsafe values disabled: failed")
end
print("All tests passed.")
do
print("\nSerializing large numeric-only tables:")
local a, str = {} local a, str = {}
for i = 1, 100000 do a[i] = i end for i = 1, 100000 do a[i] = i end
@ -278,5 +305,3 @@ do
str = serpent.dump(a, {sparse = false}) str = serpent.dump(a, {sparse = false})
print("dump/sparse=false: "..(os.clock() - start), #str) print("dump/sparse=false: "..(os.clock() - start), #str)
end end
print("All tests passed.")