Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ MUSHclient ➜ Python ➜ var.lua

var.lua

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


Posted by Isthiriel   (113 posts)  Bio
Date Mon 24 Dec 2007 10:28 AM (UTC)

Amended on Thu 27 Dec 2007 11:56 AM (UTC) by Isthiriel

Message
So, I was reading a thread over in MUSHclient/General and Nick mentioned a var.lua file that shipped with MUSHclient.

This particular file introduces a global object var that is a proxy for the world.GetVariable/world.SetVariable functions and since the script file for my current world has one of these functions on every other line, I thought this was Cool. (Save me half my typing, nearly :D)

So, I wrote a Python version. It's more verbose than the Lua version because I went through the Python manual and glued every feature on to it that I could find.

As an example:
for k, v in var.iteritems():
    world.Note("%s = %s" % (k, v))

var.stuff = { 'a': 1, 'b': 2 }
world.Note(var['stuff']['a'])

All work. In fact, you can pretty much treat var as a dict :) (In hindsight, being able to iterate over all the variables is overkill. Like I said, every feature I could find in the manual.)

I also added functionality for GetPluginVariable/... so:
pvar = PluginVar("a42fbf74f394d8129df956ae")
pvar.hp = 12

Should also work. (Not completely tested since I don't have any plugins ... everything is in my main script file :-/ )


If you don't want to paste it directly into your main script file, you can put it in a separate .py file in your script directory and then import it. (I have it in my mushclient.py with all the error codes and other utility odds-and-ends.)

Of course WSH's support of Python's import statement is a bit broken, so you may need to have the following above the 'from var import *' line:
import sys, os
cwd = r"C:\Program Files\MUSHclient\scripts"
os.chdir(cwd)
if not cwd in sys.path:
    sys.path.append(cwd)

Replacing the pathname with the appropriate value for your installation.


EDIT:Erm. Ignore that. I forgot you can't find world from an imported .py. *sigh*

EDIT-2:BUT you can find world if you execfile() the .py/.pys file instead. So my main script file is down to 80 lines or so with another thousand included with execfile()

Oh and lacking any obvious way of determining the difference between a string that is a pickled object and a string that might be a pickled object I prefixed pickles with PICKLE_SIGNATURE which is set to "~~~" below. If you intend to put a string starting with ~~~ into a variable then you will need to change the value of PICKLE_SIGNATURE. (It can be anything, including "PICKLE_SIGNATURE" if you wanted to... just be wary of the overhead involved if you set it to something long and then pickle lots of objects.)

By default it uses Python's pickle0 protocol, which is pure us-ascii (no \x00, nothing with bit7 set) and is probably overly-safe for MUSHclient's variables.

... having said that, it avoids pickling obvious strings (ie str and unicode objects) allowing them to be used in @expansions so they still have the same line-ending confusion as per usual. bools, ints, floats and complexes should also all be stored in a fashion conducive to both @expansion and them coming back with the same type they went in with (so no need to coerce them back to the proper type when getting them).

And I avoided the bool(str(False)) == True issue too :D

(Code deferred to followup since it's 5757 characters by itself.)
Top

Posted by Isthiriel   (113 posts)  Bio
Date Reply #1 on Mon 24 Dec 2007 10:29 AM (UTC)

Amended on Mon 24 Dec 2007 10:57 AM (UTC) by Isthiriel

Message
## Python equivalent of the var.lua file
import pickle

## basic version
#def mcpickle(o):
#    return str(o)
#
#def mcunpickle(s):
#    return s

PICKLE_SIGNATURE = "~~~"

def mcpickle(o):
    if isinstance(o, (bool, int, long, float, complex, str, unicode)):
        return str(o)
    # we have a non-simple type, time for pickling!
    return PICKLE_SIGNATURE + pickle.dumps(o)

def mcunpickle(s):
    if s.startswith(PICKLE_SIGNATURE):
        # unpickle
        return pickle.loads(s[len(PICKLE_SIGNATURE):])
    if s.lower() == 'true':
        return True
    if s.lower() == 'false':
        return False
    try:
        return int(s) # also catches long
    except ValueError:
        pass
    try:
        return float(s)
    except ValueError:
        pass
    try:
        if s.startswith('(') and s.endswith(')'): # might be complex!
            return complex(s[1:-1])
        return complex(s)
    except ValueError:
        pass
    # probably an actual string
    return s

class varIterator(object):
    def __init__(self, klass, rettype='name'):
        self.klass = klass
        self.variable_list = klass.keys()
        self.rettype = rettype
    def __iter__(self):
        return self
    def next(self):
        value = None
        while value == None and len(self.variable_list):
            name = self.variable_list.pop(0)
            value = klass[name]
        if value == None and len(self.variable_list) == 0:
            raise StopIteration
        if self.rettype == 'value':
            return value
        if self.rettype == 'pairs':
            return (name, value)
        return name

class var(object):
    def __getvar(self, name):
        return world.GetVariable(name)
    def __setvar(self, name, value):
        return world.SetVariable(name, value)
    def __delvar(self, name):
        return world.DeleteVariable(name)
    def __getvarlist(self):
        return world.GetVariableList
    def __getitem__(self, name):
        r = self.__getvar(name)
        if None == r:
            raise KeyError(name)
        return mcunpickle(r)
    def __setitem__(self, name, value):
        if value == None:
            r = self.__delvar(name)
        else:
            r = self.__setvar(name, mcpickle(value))
        if r == eVariableNotFound:
            raise KeyError(name)
        if r == eInvalidObjectLabel:
            raise TypeError("'%s' is not a valid MUSHclient variable name" % name)
    def __delitem__(self, name):
        r = self.__delvar(name)
        if r == eVariableNotFound:
            raise KeyError(name)
        if r == eInvalidObjectLabel:
            raise TypeError("'%s' is not a valid MUSHclient variable name" % name)
    def keys(self):
        return sorted(list(self.__getvarlist()))
    def values(self):
        return [e for e in self.itervalues()]
    def items(self):
        return [e for e in self.iteritems()]
    def __len__(self):
        return len(self.keys())
    def __iter__(self):
        return self.iterkeys()
    def iterkeys(self):
        return varIterator(self)
    def itervalues(self):
        return varIterator(self, 'value')
    def iteritems(self):
        return varIterator(self, 'pairs')
    def __contains__(self, name):
        try:
            self[name]
            return True
        except KeyError:
            return False
    def __getattr__(self, name):
        try:
            return self.__getitem__(name)
        except KeyError:
            raise AttributeError("MUSHclient has no variable '%s'" % name)
        except TypeError:
            raise SyntaxError("'%s' is not a valid MUSHclient variable name" % name)
    def __setattr__(self, name, value):
        try:
            return self.__setitem__(name, value)
        except KeyError:
            raise AttributeError("MUSHclient has no variable '%s'" % name)
        except TypeError:
            raise SyntaxError("'%s' is not a valid MUSHclient variable name" % name)
    def __delattr__(self, name):
        try:
            self.__delitem__(name)
        except KeyError:
            raise AttributeError("MUSHclient has no variable '%s'" % name)
        except TypeError:
            raise SyntaxError("'%s' is not a valid MUSHclient variable name" % name)
    def __str__(self):
        return repr(self)
    def __repr__(self):
        return repr(tuple(self.keys()))
class PluginVar(var):
    def __new__(klass, plugin, *p, **k):
        if not '_the_instance_dict' in klass.__dict__:
            klass._the_instance_dict = {}
        if not plugin in klass._the_instance_dict:
            klass._the_instance_dict[plugin] = object.__new__(klass, *p, **k)
        return klass._the_instance_dict[plugin]
    def __init__(self, plugin):
        self.plugin = plugin
        if not world.IsPluginInstalled(self.plugin):
            raise ValueError("Plugin '%s' is not installed" % self.plugin)
        self.checkEnabled()
    def isPluginEnabled(self):
        return bool(world.GetPluginInfo(self.plugin, 17))
    def checkEnabled(self):
        if not self.isPluginEnabled():
            raise ValueError("Plugin '%s' is not enabled" % self.plugin)
    def __getvar(self, name):
        self.checkEnabled()
        return world.GetPluginVariable(self.plugin, name)
    def __setvar(self, name, value):
        self.checkEnabled()
        return world.SetPluginVariable(self.plugin, name, value)
    def __delvar(self, name):
        self.checkEnabled()
        return world.DeletePluginVariable(self.plugin, name)
    def __getvarlist(self):
        self.checkEnabled()
        return world.GetPluginVariableList(self.plugin)
var = var()
Top

Posted by Worstje   Netherlands  (899 posts)  Bio
Date Reply #2 on Mon 24 Dec 2007 03:02 PM (UTC)

Amended on Mon 24 Dec 2007 03:09 PM (UTC) by Worstje

Message
You have too much time on your hands. Nice work, though.

I'd totally use it, if, you know, I could be bothered to port my existing code. Sadly having 15k+ of code kind of makes me not want to. Rue my 700+ triggers.

Edit:

Hrm, I have no time to load and study your code upclose at the moment (yay for christmas preparations and all that), but there are some things I was wondering about at a closer glance...

A string like '012', how will that come out? Won't it get butchered with octal representation with your pickle functions?

Likewise, what is the advantage of your pickle function over the one mentioned elsewhere in the forums? I've used that other one for quite some time without any problems at all.

Also, can you explain what you mean by the bool(str(False)) == True issue? I can't recall ever hearing of such an issue.
Top

Posted by Isthiriel   (113 posts)  Bio
Date Reply #3 on Mon 24 Dec 2007 04:32 PM (UTC)

Amended on Mon 24 Dec 2007 04:39 PM (UTC) by Isthiriel

Message
Not too much time on my hands, just working on your code can be frustrating :P

M:\Users\Owner>python
Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> int('012')
12
>>>

So, octal butchered, no. (Unless 012 is supposed to be octal for 10.) However if you stash a string that can be parsed as an int (specifically int(s) doesn't throw a ValueError), it will come back as an int. '012' can be parsed as an int.

You probably shouldn't be storing strings-that-are-really-ints anyway, which just leaves strings-that-look-like-ints-but-aren't-really. Of which, I can't think of a concrete example? Maybe storing "0000" or similar to remember some padding?

What other pickle function on the forum? (If you mean: http://www.gammon.com.au/forum/bbshowpost.php?id=5410&page=3 then the answer is that function will turn the string "stuff" into "S'stuff'\np0\n.", which if you're trying to write a targetting script or something else that relies on @variable expansion in triggers or aliases, won't work. Also that can be written simpler by:
def GetPickleVariable(MUSHVariable, Default=None):
    p = world.GetVariable(MUSHVariable)
    if None == p:
        return Default
    p = p.replace("\r", '')
    p = p.encode('utf8')
    return pickle.loads(p)
and you can add the two important lines (replace, encode) to mcunpickle if it doesn't work without it.)

ie.

def mcunpickle(s):
    if s.startswith(PICKLE_SIGNATURE):
        # unpickle
        s = s[len(PICKLE_SIGNATURE):]
        s = s.replace("\r", '')
        s = s.encode('utf8')
        return pickle.loads(s)
    if s.lower() == 'true':
        return True
    if s.lower() == 'false':
        return False
    ...

str(False) == 'False'
bool('False') == True
bool('') == False # and this is the only string for which this is True.
bool() is probably the same thing as __nonzero__(), which for a string is the same as != ''

So, bool() doesn't invert str() very well.
int() and float() invert str() perfectly
complex() doesn't seem to understand the parentheses around the value that str() puts there, but if you remove them it inverts str() fine.
Top

Posted by Isthiriel   (113 posts)  Bio
Date Reply #4 on Thu 27 Dec 2007 09:08 AM (UTC)

Amended on Thu 27 Dec 2007 10:58 AM (UTC) by Isthiriel

Message
Mark #2 now that I've discovered UserDict.DictMixin. *sigh*

This version also fixes some bugs in the original.

# ---------------------------------------------------------
# Python version of var.lua
# ---------------------------------------------------------
import UserDict

PICKLE_SIGNATURE = "~~~"

def mcpickle(o):
    if isinstance(o, (bool, int, long, float, complex, str, unicode)):
        return str(o)
    # we have a non-simple type, time for pickling!
    return PICKLE_SIGNATURE + pickle.dumps(o)

def mcunpickle(s):
    if s.startswith(PICKLE_SIGNATURE):
        # unpickle
        s = s[len(PICKLE_SIGNATURE):]
        # Worstje tells me these are necessary to avoid a corrupt pickle
        s = s.replace("\r", '')
        s = s.encode('utf8')
        return pickle.loads(s)
    if s.lower() == 'true':
        return True
    if s.lower() == 'false':
        return False
    try:
        return int(s) # also catches long
    except ValueError:
        pass
    try:
        return float(s)
    except ValueError:
        pass
    try:
        if s.startswith('(') and s.endswith(')'): # might be complex!
            return complex(s[1:-1])
        return complex(s)
    except ValueError:
        pass
    # probably an actual string
    return s

class wvar(object, UserDict.DictMixin):
    __repr__ = UserDict.DictMixin.__repr__
    def getvar(self, name):
        return world.GetVariable(name)
    def setvar(self, name, value):
        return world.SetVariable(name, value)
    def delvar(self, name):
        return world.DeleteVariable(name)
    def getvarlist(self):
        return world.GetVariableList
    def __getitem__(self, name):
        r = self.getvar(name)
        if None == r:
            raise KeyError(name)
        return mcunpickle(r)
    def __setitem__(self, name, value):
        if value == None:
            r = self.delvar(name)
        else:
            r = self.setvar(name, mcpickle(value))
        if r == eVariableNotFound:
            raise KeyError(name)
        if r == eInvalidObjectLabel:
            raise TypeError("'%s' is not a valid MUSHclient variable name" % name)
    def __delitem__(self, name):
        r = self.delvar(name)
        if r == eVariableNotFound:
            raise KeyError(name)
        if r == eInvalidObjectLabel:
            raise TypeError("'%s' is not a valid MUSHclient variable name" % name)
    def keys(self):
        return sorted(list(self.__getvarlist()))
    def __getattr__(self, name):
        try:
            return self.__getitem__(name)
        except KeyError:
            raise AttributeError("MUSHclient has no variable '%s'" % name)
        except TypeError:
            raise SyntaxError("'%s' is not a valid MUSHclient variable name" % name)
    def __setattr__(self, name, value):
        try:
            self.__setitem__(name, value)
        except KeyError:
            raise AttributeError("MUSHclient has no variable '%s'" % name)
        except TypeError:
            raise SyntaxError("'%s' is not a valid MUSHclient variable name" % name)
    def __delattr__(self, name):
        try:
            self.__delitem__(name)
        except KeyError:
            raise AttributeError("MUSHclient has no variable '%s'" % name)
        except TypeError:
            raise SyntaxError("'%s' is not a valid MUSHclient variable name" % name)

class PluginVar(wvar):
    def __new__(klass, plugin, *p, **k):
        if not '_the_instance_dict' in klass.__dict__:
            klass._the_instance_dict = {}
        if not plugin in klass._the_instance_dict:
            klass._the_instance_dict[plugin] = wvar.__new__(klass, *p, **k)
        return klass._the_instance_dict[plugin]
    def __init__(self, plugin):
        wvar.__init__(self)
        self.plugin = plugin
        if not world.IsPluginInstalled(self.plugin):
            raise ValueError("Plugin '%s' is not installed" % self.plugin)
        self.checkEnabled()
    def isPluginEnabled(self):
        return bool(world.GetPluginInfo(self.plugin, 17))
    def checkEnabled(self):
        if not self.isPluginEnabled():
            raise ValueError("Plugin '%s' is not enabled" % self.plugin)
    def getvar(self, name):
        self.checkEnabled()
        return world.GetPluginVariable(self.plugin, name)
    def setvar(self, name, value):
        self.checkEnabled()
        return world.SetPluginVariable(self.plugin, name, value)
    def delvar(self, name):
        self.checkEnabled()
        return world.DeletePluginVariable(self.plugin, name)
    def getvarlist(self):
        self.checkEnabled()
        return world.GetPluginVariableList(self.plugin)

wvar = wvar()


That trimmed about half of the lines. :-/
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.


17,571 views.

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

Go to topic:           Search the forum


[Go to top] top

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