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))
if err then error(err) end
local copy = fun()
-- or using serpent.load:
local ok, copy = serpent.load(serpent.dump(a))
print(ok and copy[3] == a[3])
```
## 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`.
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
* indent (string) -- indentation; triggers long multi-line output
@ -167,6 +177,10 @@ See LICENSE file.
## History
### v0.26 (Nov 05 2013)
- Added `load` function that (safely) loads serialized/pretty-printed values.
- Updated documentation.
### v0.25 (Sep 29 2013)
- Added `maxnum` option to limit the number of elements in tables.
- 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 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}
@ -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"
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
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,
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 }

View File

@ -261,8 +261,35 @@ do
assert(called == false, "sorting is not called on numeric-only tables with maxnum: failed")
end
-- test serializing large numeric-only tables
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 = {}
for i = 1, 100000 do a[i] = i end
@ -278,5 +305,3 @@ do
str = serpent.dump(a, {sparse = false})
print("dump/sparse=false: "..(os.clock() - start), #str)
end
print("All tests passed.")