Posted by
| Nick Gammon
Australia (23,140 posts) Bio
Forum Administrator |
Message
| While you were replying I made a test plugin to show the idea...
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Friday, April 07, 2006, 10:41 AM -->
<!-- MuClient version 3.74 -->
<!-- Plugin "serialize_test" generated by Plugin Wizard -->
<muclient>
<plugin
name="serialize_test"
author="Nick Gammon"
id="22d87669195ffdc6f3b4104d"
language="Lua"
purpose="Tests serializing Lua variables"
save_state="y"
date_written="2006-04-07 10:38:38"
requires="3.74"
version="1.0"
>
</plugin>
<!-- Aliases -->
<aliases>
<alias
match="^cfg_show$"
enabled="y"
regexp="y"
send_to="12"
keep_evaluating="y"
>
<send>Note(cfg.test)</send>
</alias>
<alias
match="^cfg_set (.+)$"
enabled="y"
regexp="y"
send_to="12"
keep_evaluating="y"
>
<send>cfg.test = "%1"</send>
</alias>
</aliases>
<!-- Script -->
<script>
<![CDATA[
-- ----------------------------------------------------------
-- serializer
-- See "Programming In Lua" chapter 12.1.2.
-- Also see forum thread:
-- http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=4960
-- ----------------------------------------------------------
function basicSerialize (o)
if type(o) == "number" or type(o) == "boolean" then
return tostring(o)
else -- assume it is a string
return string.format("%q", o)
end
end -- basicSerialize
--
-- Lua keywords might look OK to not be quoted as keys but must be.
-- So, we make a list of them.
--
lua_reserved_words = {}
for _, v in pairs ({
"and", "break", "do", "else", "elseif", "end", "false",
"for", "function", "if", "in", "local", "nil", "not", "or",
"repeat", "return", "then", "true", "until", "while"
}) do lua_reserved_words [v] = true end
-- ----------------------------------------------------------
-- save one variable (calls itself recursively)
--
-- Modified on 23 October 2005 to better handle keys (like table keys)
-- ----------------------------------------------------------
function save (name, value, out, indent, saved)
saved = saved or {} -- initial value
indent = indent or 0 -- start indenting at zero cols
local iname = string.rep (" ", indent) .. name -- indented name
-- numbers, strings, and booleans can be simply serialized
if type(value) == "number" or
type(value) == "string" or
type(value) == "boolean" then
table.insert (out, iname .. " = " .. basicSerialize(value))
-- tables need to be constructed, unless we have already done it
elseif type(value) == "table" then
if saved[value] then -- value already saved?
table.insert (out, iname .. " = " .. saved[value]) -- use its previous name
else
-- remember we have created this table so we don't do it twice
saved[value] = name -- save name for next time
-- make the table constructor, and recurse to save its contents
table.insert (out, iname .. " = {}") -- create a new table
local k, v
for k, v in pairs (value) do -- save its fields
local fieldname
-- if key is a Lua variable name which is not a reserved word
-- we can express it as tablename.keyname
if type (k) == "string"
and string.find (k, "^[_%a][_%a%d]*$")
and not lua_reserved_words [k] then
fieldname = string.format("%s.%s", name, k)
-- if key is a table itself, and we know its name then we can use that
-- eg. tablename [ tablekeyname ]
elseif type (k) == "table" and saved[k] then
fieldname = string.format("%s[%s]", name, saved [k])
-- if key is an unknown table, we have to raise an error as we cannot
-- deduce its name
elseif type (k) == "table" then
error ("Key table entry " .. tostring (k) ..
" in table " .. name .. " is not known")
-- if key is a number or a boolean it can simply go in brackets,
-- like this: tablename [5] or tablename [true]
elseif type (k) == "number" or type (k) == "boolean" then
fieldname = string.format("%s[%s]", name, tostring (k))
-- now key should be a string, otherwise an error
elseif type (k) ~= "string" then
error ("Cannot serialize table keys of type '" .. type (k) ..
"' in table " .. name)
-- if key is a non-variable name (eg. "++") then we have to put it
-- in brackets and quote it, like this: tablename ["keyname"]
else
fieldname = string.format("%s[%s]", name,
basicSerialize(k))
end
-- now we have finally worked out a suitable name for the key,
-- recurse to save the value associated with it
save(fieldname, v, out, indent + 2, saved)
end
end
-- cannot serialize things like functions, threads
else
error("Cannot save a " .. type(value))
end
end -- save
-- ----------------------------------------------------------
-- Serialize a variable or nested set of tables:
-- ----------------------------------------------------------
--[[
Example of use:
SetVariable ("mobs", serialize ("mobs")) --> serialize mobs table
loadstring (GetVariable ("mobs")) () --> restore mobs table
If you need to serialize two tables where subsequent ones refer to earlier ones
you can supply your own "saved tables" variable, like this:
t = {}
result = serialize ("mobs", nil, t)
result = result .. "\n" .. serialize ("quests", nil, t)
In this example the serializing of "quests" also knows about the "mobs" table
and will use references to it where necessary.
You can also supply the actual variable if the variable to be serialized does
not exist in the global namespace (for instance, if the variable is a local
variable to a function). eg.
do
local myvar = { 1, 2, 8, 9 }
print (serialize ("myvar", myvar))
end
In this example, without supplying the location of "myvar" the serialize would fail
because it would not be found in the _G namespace.
--]]
function serialize (what, v, saved)
v = v or _G [what] -- default to "what" in global namespace
assert (type (what) == "string",
"1st argument to serialize should be the *name* of a variable")
assert (v, "Variable '" .. what .. "' does not exist")
assert (type (saved) == "table" or type (saved) == "nil",
"3rd argument to serialize should be a table or nil")
local out = {} -- output to this table
save (what, v, out, nil, saved) -- do serialization
return table.concat (out, "\r\n") -- turn into a string
end -- serialize
cfg = {}
-- SAVE VARIABLES
function OnPluginSaveState ()
SetVariable("skoid_cfg", serialize("cfg"))
end -- OnPluginSaveState
function OnPluginInstall ()
loadstring (GetVariable("skoid_cfg") or "") ()
end -- OnPluginInstall
]]>
</script>
</muclient>
This seems to work fine. To prove things are changing I modified the alias, so I can type something like:
cfg_set nick
Now if you reinstall the plugin (which forces it to close and re-open), the state file now looks like this:
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Friday, April 07, 2006, 10:51 AM -->
<!-- MuClient version 3.74 -->
<!-- Written by Nick Gammon -->
<!-- Home Page: http://www.muclient.com/ -->
<!-- Plugin state saved. Plugin: "serialize_test". World: "SMAUG". -->
<muclient>
<!-- variables -->
<variables
muclient_version="3.74"
world_file_version="15"
date_saved="2006-04-07 10:51:59"
>
<variable name="skoid_cfg">cfg = {}
cfg.test = "nick"</variable>
</variables>
</muclient>
As you can see the variable is set, and when you do a cfg_show, it displays "nick".
Note the use of OnPluginInstall for loading the variable, and:
GetVariable("skoid_cfg") or ""
This handles the case of the variable not existing yet.
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|