[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Tips and tricks
. . -> [Subject]  MUSHclient variables in a lua table

MUSHclient variables in a lua table

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page


Posted by Ircria   (24 posts)  [Biography] bio
Date Sat 05 Mar 2011 11:56 AM (UTC)

Amended on Sat 05 Mar 2011 11:58 AM (UTC) by Ircria

Message
After seeing a friend's code for returning tables from mushclient variables while emulating var.lua, I saw an improvement in their code that could be made.

require "serialize"

function mushvartable(table)
  return setmetatable({}, {
    __index = function (table, key)
      if not GetVariable(key) then
        return nil
      elseif not tonumber(GetVariable(key)) then
        if string.match(GetVariable(key), "{") and string.match(GetVariable(key), "}") then
          local retp = GetVariable(key)
          loadstring("local ret = "..retp)()
          return ret
        else
          return GetVariable(key)
        end
      else
        return tonumber(key)
      end
    end,
    __newindex = function(table, key, value)
      if type(value) == 'nil' then
        DeleteVariable(key)
      elseif type(value) == 'table' then
        SetVariable(key, serialize.save_simple(value))
      else
        SetVariable(key, value)
      end
    end,
    __metatable = false
  });
end

mushvars = mushvartable {}


This allows one to use the mushvars table to directly edit the MUSHclient variables without the need to type out SetVariable(), and automatically serializes and unserializes tables to and from MUSHclient variables so they can be read as if they were normal tables. (basically, the built in var.lua with table functionality). If it can be tonumber()ed, it will also return it in number form instead of a string.

Usage: Just treat it like a normal lua table.

mushvars.test = "test"
mushvars.test1 = 1
mushvars.testtab = {"whathaveyou", i = "am a named variable", {"i am in a table"}}
testvars.test = nil --deletes the variable
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #1 on Sat 05 Mar 2011 09:21 PM (UTC)

Amended on Sat 05 Mar 2011 09:25 PM (UTC) by Twisol

Message
If I may suggest another simplifying change:

require "serialize"

function mushvartable(table)
  return setmetatable({}, {
    __index = function (table, key)
      local v = GetVariable(key)
      if v then
        local f = loadstring("return " .. GetVariable(key))
        if f then
          return f()
        end
      end
      -- fallback
      return v
    end,
    __newindex = function(table, key, value)
      if value == nil then
        DeleteVariable(key)
      else
        SetVariable(key, serialize.save_simple(value))
      end
    end,
    __metatable = false
  });
end

mushvars = mushvartable {}


It takes advantage of serialize.save_simple's ability to save primitive types as well as tables, so save_simple(1) becomes "1" and save_simple("foo") becomes (more or less) "\"foo\"". It falls back to just returning the raw variable if it couldn't be loadstring()'d.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #2 on Sat 05 Mar 2011 09:42 PM (UTC)
Message
Hmmm. I reworked the idea a bit. First to remove multiple calls to GetVariable, which would slow it down. Second to add a couple more features:


  • Also convert "true" and "false" back to booleans
  • Handle bad compile when attempting to convert tables back
  • Table checks now for "{ xxx }" not just "{" and "}" somewhere in the string
  • Loadstring done in a restricted environment to stop data injection like:

    
    var = "{ hello }; os.remove 'mushclient.exe'; b = {}"
    





-- tvar.lua
-- ----------------------------------------------------------
-- Accessing MUSHclient variables through the 'var' table.

-- See forum thread:
--  http://www.gammon.com.au/forum/?id=4904

-- Improvements suggested:
--  http://www.gammon.com.au/forum/?id=10980

-- Date: 6th Marh 2011

--[[

  * Set a variable by assigning something to it.
  * Delete a variable by assigning nil to it.
  * Get a variable by retrieving its value, will return nil if the variable does not exist.
  
  Improvements in this version:
  
  * Automatically turns things that look like numbers into numbers
  * Automatically turns "true" and "false" into booleans
  * Saves amd restores tables by using serialize
  
  Examples:

    require "tvar"
    
    var.target = "kobold"   -- set MUSHclient variable 'target' to kobold
    print (var.target)      -- print contents of MUSHclient variable

    -- table
    var.t1 = { "fish", "chips", "steak", { "subtable", "foo" }, drink = "beer" }
    
    
  Limitations:
  
    1. Things that look like numbers will become numbers, perhaps unintentionally
    2. Things that look like tables will become tables, perhaps unintentionally
    3. The words "true" and "false" will become booleans even if they were not before
    4. Slightly slower than the "var" module because of the extra tests
    
--]]

-- ----------------------------------------------------------

require "serialize"

var = {}  -- variables table

setmetatable (var, 
 { 
 -- called to access an entry
 __index = 
 function (t, key) 
 
  local v = GetVariable (key)  -- find the variable in MUSHclient variable space
  
  -- no such variable returns nil
  if v == nil then
     return nil
  end -- if

  -- convert true and false back into booleans
  if v == "true" then
    return true
  end -- if true

  if v == "false" then
    return false
  end -- if false
    
  -- if can be converted into a number return that number
  local n = tonumber (v)
  if n then 
    return n
  end -- if a number
  
  -- if variable is in the form {xxx} treat as a table
  if string.sub (v, 1, 1) == "{" and string.sub (v, -1, -1) == "}" then
    local t = {}  -- local environment
    local f = loadstring ("ret = " .. v)
    -- if loadstring fails, just return as a string
    if f then
      setfenv (f, t) -- local environment
      -- if pcall fails, just return as a string
      if pcall (f) then
        return t.ret
      end -- if executed ok
    end -- if loadstring worked
  end -- if looks like a table
  
  return v  -- return "normal" variable    
  
 end,  -- function __index
 
 -- called to change or delete an entry
 __newindex = 
  function (t, key, value) 
    local result  -- of SetVariable
    
    if value == nil then -- nil deletes it
      result = DeleteVariable (key)
    elseif type (value) == 'table' then
      result = SetVariable (key, serialize.save_simple (value))
    else
      result = SetVariable (key, tostring (value)) 
    end -- if
    
    -- warn if they are using bad variable keys
    if result == error_code.eInvalidObjectLabel then
     error ("Bad variable key '" .. key .. "'", 2)
    end  -- bad variable
  end  -- function __newindex
 })  -- end metatable
 
return var



I should point out that this new module is not necessarily symmetrical.

For example, something that you wanted to be a string, but happens to look like a number (eg. "5e6") now actually becomes a number (eg. 5000000).

Similarly the strings "true" and "false" become booleans, when you might not want that.

Similarly something that looks like a table now becomes one (eg. "{nick}" becomes an empty table because the variable nick is nil).

Still, if you can live with that, you may find it useful.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #3 on Sat 05 Mar 2011 09:43 PM (UTC)
Message
Twisol said:

If I may suggest another simplifying change:


Ninja'd while I was doing my lengthy changes. ;)

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #4 on Sat 05 Mar 2011 10:04 PM (UTC)

Amended on Sat 05 Mar 2011 10:05 PM (UTC) by Twisol

Message
^_^

Nick Gammon said:
*Also convert "true" and "false" back to booleans
*Handle bad compile when attempting to convert tables back
*Table checks now for "{ xxx }" not just "{" and "}" somewhere in the string
*Loadstring done in a restricted environment to stop data injection like:


var = "{ hello }; os.remove 'mushclient.exe'; b = {}"

My version handles all of these except the injection attack. Good catch!


Nick Gammon said:

    1. Things that look like numbers will become numbers, perhaps unintentionally
    2. Things that look like tables will become tables, perhaps unintentionally
    3. The words "true" and "false" will become booleans even if they were not before
    4. Slightly slower than the "var" module because of the extra tests

My version assumes that all input was sent through serialize.save_simple(), so it's always consistent.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #5 on Sat 05 Mar 2011 10:10 PM (UTC)

Amended on Sat 05 Mar 2011 10:30 PM (UTC) by Twisol

Message
Merger of the two versions:


-- tvar.lua
-- ----------------------------------------------------------
-- Accessing MUSHclient variables through the 'var' table.

-- See forum thread:
--  http://www.gammon.com.au/forum/?id=4904

-- Improvements suggested:
--  http://www.gammon.com.au/forum/?id=10980

-- Date: 6th Marh 2011

--[[

  * Set a variable by assigning something to it.
  * Delete a variable by assigning nil to it.
  * Get a variable by retrieving its value, will return nil if the variable does not exist.
  
  Improvements in this version:
  
  * Saves and restores primitive types and tables by using the serialize module
  
  Examples:

    require "tvar"
    
    var.target = "kobold"   -- set MUSHclient variable 'target' to kobold
    print (var.target)      -- print contents of MUSHclient variable

    -- table
    var.t1 = { "fish", "chips", "steak", { "subtable", "foo" }, drink = "beer" }
    
    
  Limitations:
  
    1. Slightly slower than the "var" module because of the extra tests
    
--]]

-- ----------------------------------------------------------

require "serialize"

var = {}  -- variables table

setmetatable (var, {
  -- called to access an entry
  __index = function (t, key) 
    local v = GetVariable (key)  -- find the variable in MUSHclient variable space
    if v then
      local env = {}
      local f = loadstring ("return " .. v)
      if f then
        local ok, result = pcall (setfenv (f, env))
        if ok then
          return result
        end
      end
    end
    
    -- fallback
    return v
  end,  -- function __index
 
  -- called to change or delete an entry
  __newindex = function (t, key, value) 
    local result  -- of SetVariable
    
    if value == nil then -- nil deletes it
      result = DeleteVariable (key)
    else
      result = SetVariable (key, serialize.save_simple (value))
    end -- if
    
    -- warn if they are using bad variable keys
    if result == error_code.eInvalidObjectLabel then
     error ("Bad variable key '" .. key .. "'", 2)
    end  -- bad variable
  end  -- function __newindex
})  -- end metatable

return var


[EDIT]: Incorporate pcall() too.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #6 on Sun 06 Mar 2011 12:38 AM (UTC)
Message
Twisol said:

My version assumes that all input was sent through serialize.save_simple(), so it's always consistent.


They are just MUSHclient variables, right? It's not like they are plugin state files over which you normally have more control.

I can imagine someone might set a variable in an alias or trigger, and then use this method to retrieve it. And who knows, someone may call their character "{}; DoCommand 'quit'; {}". I know, pretty unlikely. But if you were capturing the last chat message into a variable, perhaps a little more likely.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #7 on Sun 06 Mar 2011 03:13 AM (UTC)

Amended on Sun 06 Mar 2011 03:27 AM (UTC) by Twisol

Message
Nick Gammon said:

Twisol said:

My version assumes that all input was sent through serialize.save_simple(), so it's always consistent.


They are just MUSHclient variables, right? It's not like they are plugin state files over which you normally have more control.

I can imagine someone might set a variable in an alias or trigger, and then use this method to retrieve it. And who knows, someone may call their character "{}; DoCommand 'quit'; {}". I know, pretty unlikely. But if you were capturing the last chat message into a variable, perhaps a little more likely.

Well, if you want raw variable access, you probably want the original var.lua module. It seems to me that tvar is slanted towards structured data. That said, in your example it won't deserialize anyways, because I call loadstring as loadstring("return " .. v) rather than setting a local variable. And since it errors out, it will fall back to providing the raw variable.

I may be in the minority, but to be honest I rarely use the Variable field in the editor. Normally I just use SetVariable("foo", "%1") manually because it's more explicit. In this case you could just use tvar.foo = "%1".


Errata: I should have said "assumes variables contain output from" rather than "assumes all input was sent through".

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (22,975 posts)  [Biography] bio   Forum Administrator
Date Reply #8 on Sun 06 Mar 2011 04:28 AM (UTC)
Message
Twisol said:

That said, in your example it won't deserialize anyways, because I call loadstring as loadstring("return " .. v) rather than setting a local variable.


Oh well, you could do:


"{a = DoCommand 'ExitClient' }"


However your setfenv protects against that. But at least it is legal syntax:


return {a = DoCommand 'ExitClient' }




- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #9 on Sun 06 Mar 2011 04:33 AM (UTC)

Amended on Sun 06 Mar 2011 04:34 AM (UTC) by Twisol

Message
Yep, that's true. Your version would do the same thing however. You just have to be consistent: either use tvar for getting and setting, or use the core API functions. tvar acts as an intermediary serialization layer, so it's not unreasonable to say "if you don't do this you won't get what you expect".

As a sidenote: thank you Ircria for the great contribution!

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


30,615 views.

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]