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


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Python
. . -> [Subject]  world.GetOption proxy

world.GetOption proxy

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


Posted by Isthiriel   (113 posts)  [Biography] bio
Date Thu 27 Dec 2007 09:56 AM (UTC)

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

Message
In the style of my var.lua replacement, a similar dict-like object that proxies world.GetOption/world.GetAlphaOption/...

# ---------------------------------------------------------
# Python world.GetOption proxy dictionary
# ---------------------------------------------------------

import UserDict # if you don't have it already

class gopt(object, UserDict.DictMixin):
    __repr__ = UserDict.DictMixin.__repr__
    def canonize_name(self, name):
        # Global Options don't seem to have a canonical form.
        # In particular, "DefaultInputFontItalic " <-- trailing space
        # And "Tray Icon", "Icon Placement",
        #name = name.split()
        #if len(name) > 1:
        #    world.Note(repr(name))
        #    name = name[0] + [n.lower().capitalize() for n in name[1:]]
        #name = u"".join(name)
        ## Why, oh why!?
        #if name == u"DefaultInputFontItalic":
        #    name += " "
        return name
    def getopt(self, name):
        return world.GetGlobalOption(name)
    def setopt(self, name, value):
        raise TypeError("Cannot modify global options")
    def __getitem__(self, name):
        name = self.canonize_name(name)
        r = self.getopt(name)
        if None == r:
            raise KeyError(name)
    def __setitem__(self, name, value):
        assert value != None
        name = self.canonize_name(name)
        r = self.setopt(name, value)
        if eUnknownOption == r:
            raise KeyError(name)
    def __delitem__(self, name):
        raise TypeError("Cannot delete MUSHclient options")
    def keys(self):
        return world.GetGlobalOptionList
    def __getattr__(self, name):
        try:
            return self.__getitem__(name)
        except KeyError:
            raise AttributeError("MUSHclient has no option '%s'" % name)
    def __setattr__(self, name, value):
        try:
            self.__setitem__(name, value)
        except KeyError:
            raise AttributeError("MUSHclient has no option '%s'" % name)
    def __delattr__(self, name):
        raise TypeError("Cannot delete MUSHclient options")

class wopt(gopt):
    def canonize_name(self, name):
        name = name.lower()
        name = name.split()
        name = '_'.join(name)
        return name
    def getopt(self, name):
        return world.GetCurrentValue(name)
    def setopt(self, name, value):
        if name in world.GetAlphaOptionList:
            if isinstance(value, str):
                value = value.decode('utf8')
            assert isinstance(value, unicode)
            return world.SetAlphaOption(name, value)
        if name in world.GetOptionList:
            if isinstance(value, bool):
                value = int(value)
            assert isinstance(value, int)
            return world.SetOption(name, value)
        return eUnknownOption
    def keys(self):
        return sorted(list(world.GetAlphaOptionList) + list(world.GetOptionList))

class lwopt(wopt):
    def getopt(self, name):
        return world.GetLoadedValue(name)
    def setopt(self, name, value):
        raise TypeError("Cannot modify loadtime option values")

class dwopt(wopt):
    def getopt(self, name):
        return world.GetDefaultValue(name)
    def setopt(self, name, value):
        raise TypeError("Cannot modify default option values")

gopt = gopt()
wopt = wopt()
lwopt = lwopt()
dwopt = dwopt()


Hmm. I wonder if I should make a full OO over all the MUSHclient functions so you never need to explicitly reference world...
[Go to top] top

Posted by Aryzu   (12 posts)  [Biography] bio
Date Reply #1 on Sun 18 Jan 2009 11:56 PM (UTC)

Amended on Mon 19 Jan 2009 02:42 AM (UTC) by Aryzu

Message
I am new to MUSHclient and looked at the Python scripting interface. I was very happy when I saw that MUSHclient is supporting Python. But after trying to use it, it seemed to be not very pythonic!

I would love to create a proper interface for that and your proxy classes are a good start. But somthing else:

- I love the duck-typing ability of Python. That means you can give a function any object you want as long as it behaves as expected. A basic example would be world.Note(world). At the current implementation this statement throws an error. But why, world does provide a string/unicode interface (world.__str__ or str(world)), acutally.

- Don't name attributes like functions! For example world.GetPluginID is of <type 'unicode'>, is supposed it is callable! Hence it should be named different, something like plugin_id

- Return error codes has to be transformed in exceptions with adequate helptext. That can be easily done based on the sample script, wich provides the ids and help texts.

- Triggers and aliases and timers should be classes. I haven't used such interfaces as GetAliasInfo but I hate them already. You should be able to do: alias.group = 'combat'

Ok that would be some work, but I think it is -realy- worth it.

I like the fact to have one world object. But a wrapper should ease the interface in the mentioned way. Personally I would add the globals, aliases and other lists to the world as an attribute: world.aliases['cast']

I have wrote that, after reading the first example in the help (world.Note...) so probably we have to do much more.

I am new to the community, what do you think? Please share your thoughts! I think Python is a very flexible and mighty language wich deserves a proper interface!

So far
Aryzu


EDIT: I have found something else:
- world.GetTriggerList should return a empty list not None, if it is empty

- Perhaps we should make a script class, containing the callback methods: OnWorldOpen, OnWorldClose, OnWorldConnect. And you have to inherit from it. Then the user have to instance his class and register it somehow. Thus you are able to generalise your scripts or give some options wich can be set up in the constructor, if you share your script.

- Why is the default script suffix '.pys'? They are valid python scripts, or aren't they?. You can't import files with *.pys and please add the script directory to the pythonpath so you can separate for example the wrapper script from the current script file.
[Go to top] top

Posted by Nick Gammon   Australia  (22,973 posts)  [Biography] bio   Forum Administrator
Date Reply #2 on Mon 19 Jan 2009 05:15 AM (UTC)
Message
Quote:

They are valid python scripts, or aren't they?. You can't import files with *.pys and please add the script directory to the pythonpath so you can separate for example the wrapper script from the current script file.


I don't understand this part. Why can't you import files? And what do you mean by "are valid python scripts, or aren't they"?



- Nick Gammon

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

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #3 on Mon 19 Jan 2009 07:43 AM (UTC)
Message
.pys is the default extension for PyScript, as set by the ActiveState Python distribution. MushClient doesn't have any links to the python engine, it uses the windows script host interface to go thru COM which goes thru pyWin32.py to get to the python engine.

As such, Mushclient is agnostic to which ActiveX engine you use (Lua is the only native engine), and doesn't set any configuration directives to your chosen script engine. You can configure the pythonpath yourself.

This is also why the object model isn't very 'pythonic'. It's very 'COMish' (I avoided COMic for you Nick ;)) and even doesn't conform to the Lua methodology of design since Lua was added in later versions.

Hope that helps. :)
[Go to top] top

Posted by Aryzu   (12 posts)  [Biography] bio
Date Reply #4 on Mon 19 Jan 2009 01:15 PM (UTC)

Amended on Mon 19 Jan 2009 01:19 PM (UTC) by Aryzu

Message
@WillFa: Thank's for clarifying that.

I thought, that the extension is hardcoded. Of course you can name your files as you want. Just add the path manually. And you can even add your script-folders to the python path. I have locked at os.curdir and it is set to C:\Documents and Settings\Administrator\

Hence importing images other file objects like databases can only be imported with absolute pathes or realtive pathes against your curdir.

@Nick Gammon: I hadn't understood why somebody would name python script files '.pys' because that has an disadvantage. If you have multiple scripts, a basic one (e.g. a wrapper class) and one with trigger callbacks. If you name them '.pys' you can't import the basic script in your trigger script. But if you name the '.py' it workes, assuming your pythonpath is adjusted. I thought about why somebody would name their script file pys, but couldn't find an answer, because it has many disadvantages. furthermore they are valid python files. I mean you can import them from a valid normal python program and they will work (with few preparations).

If you can't change the setup of the script engine it doesn't matter, but it would be nice, if the current working directory is set to the folder of your script file, or if that is to dynamic to the MUSHclient script folder.

The wrapper would be something extra. Then you could import a file and subclass a class and use it's world wrapper. It could require some tricks to get the world COMobject from the globals() or register an instance, but it should be possible.
[Go to top] top

Posted by Nick Gammon   Australia  (22,973 posts)  [Biography] bio   Forum Administrator
Date Reply #5 on Mon 19 Jan 2009 07:47 PM (UTC)
Message
When I added Python support I thought the files were suffixed .pys - I have amended the source to also allow .py files when loading Python script files. This only affects the names that show up in the file browser, of course you can use any file names.

As for the current directory, that is something that tends to change as you browse around (eg. browse for a sound file). Thus in my scripts I use GetInfo to find the various paths (eg. scripts path, MUSHclient executable path), and use that to work out exactly where to load things from.


Quote:

Willfa:

This is also why the object model isn't very 'pythonic'. It's very 'COMish' (I avoided COMic for you Nick ;)) and even doesn't conform to the Lua methodology of design since Lua was added in later versions.


The object model isn't very good, I admit. When I started doing it I wasn't really sure of the best way of doing it, and wrestling with Microsoft's COM model, and their documentation, didn't help.

If I was starting again from scratch, certainly the Lua model would be better. In fact there would probably only *be* a Lua model <evilgrin>. However don't worry, I don't plan to start again from scratch in a hurry. ;)

- Nick Gammon

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

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #6 on Tue 20 Jan 2009 02:17 AM (UTC)
Message
Quote:
The object model isn't very good, I admit.


I actually think you did a great job at making things accessible in an agnostic fashion. For example, if the original design centered on VBscript, then GetInfo probably would have been broken up into Collections, which jscript and perl would have problems with.


And for starting over with only Lua, well, it wouldn't affect me, but one of my favorite selling points to people is that it is language agnostic. So I'm glad that's not happening any time soon. :)
[Go to top] top

Posted by Isthiriel   (113 posts)  [Biography] bio
Date Reply #7 on Thu 22 Jan 2009 02:00 PM (UTC)
Message
Quote:
Aryzu wrote:
A basic example would be world.Note(world). At the current implementation this statement throws an error. But why, world does provide a string/unicode interface (world.__str__ or str(world)), acutally.

The problem there is that world.Note is not a python function, it's a COM function and the python interpreter doesn't know that it's expecting a string, so passes the world object without casting it to a string. The error comes from the COM interface, not Python.

Quote:
Aryzu wrote:
Triggers and aliases and timers should be classes. I haven't used such interfaces as GetAliasInfo but I hate them already. You should be able to do: alias.group = 'combat'

You mean like in my addxml.pys? :)
http://www.gammon.com.au/forum/?id=8398

It's not a really OO implementation, but, I do use it in the following ways:
x = XMLTrigger('NoteOnNewLine')
x.group = 'NoteOnNewLine'
x.script = 'OnNewLine'
x.send_to = eXmlSendOutput
x.send = ''
x.set_flags(eEnabled | eKeepEvaluating | eTriggerRegularExpression | eTemporary)
x.match = '$'
x.sequence = 20
x.add()

def TimerCreate():
	x = XMLTimer.get('TimerPoll')
	if not x:
		x = XMLTimer('TimerPoll')
	x.set_flags(eEnabled | eActiveWhenClosed | eTemporary)
	x.hour = 0
	x.minute = 0
	x.second = 0.1
	x.send_to = eXmlSendOutput
	x.send = ''
	x.group = 'TimerPoll'
	x.script = 'TimerPoll'
	x.add()

Of course, set_flags could probably be implemented better. (Pythonic set springs to mind.)

Quote:
Aryzu wrote:
If you name them '.pys' you can't import the basic script in your trigger script.

I was going to say that you can, because all my files are named .pys and work fine. (I take advantage of my editor's highlighting to light up the MUSHclient specific functions as well.)

However, on closer inspection, I actually have this disaster at the top of my main script file:
cwd = world.GetAlphaOption('script_filename')
cwd = os.path.split(cwd)[0]
os.chdir(cwd)
for d in [cwd, r"C:\Program Files\Python\25\Lib", r"C:\Program Files\Python\25\Lib\lib-tk", ]:
    if not d in sys.path:
        sys.path.append(d)

execfile('mushclient.pys')
execfile('world_options.pys')
execfile('world_variables.pys')

vTimer = 'ScrStartup_TimersEnabled'
try:
    wvar[vTimer]
except KeyError:
    wvar[vTimer] = wopt.enable_timers
    wopt.enable_timers = False
    wopt.save()
# ---------------------------------------------------------
# Pre-Amble above this line. DO NOT MODIFY
# ---------------------------------------------------------

ct = time.clock()
idle_timer = ct

execfile('note.pys')
execfile('log.pys')
execfile('mxp.pys')
execfile('world.pys')
execfile('addxml.pys')
execfile('timerpoll.pys')
execfile('disctime.pys')
execfile('discinfobar.pys')

The mucking about with the timers is because I have one that runs every 0.2s that produces a spectacular amount of spam if the script fails to compile. Disabling the timers here allows me to reenable them at the end of the script's main execution, assuming it all compiles.
# ---------------------------------------------------------
# Post-Amble below this line. DO NOT MODIFY
# ---------------------------------------------------------
try:
    wopt.enable_timers = wvar[vTimer]
    del wvar[vTimer]
    wopt.save()
except KeyError:
    pass
world.ColourNote('white', 'black', "Script Compiled")


execfile() does achieve most of what import does, however IIRC, when it fails, instead of getting a nice ImportError with a stacktrace, the message is fairly meaningless :(
[Go to top] top

Posted by Aryzu   (12 posts)  [Biography] bio
Date Reply #8 on Thu 22 Jan 2009 03:01 PM (UTC)

Amended on Thu 22 Jan 2009 03:08 PM (UTC) by Aryzu

Message
Quote:
Isthiriel wrote:
Quote:
Aryzu wrote:
A basic example would be world.Note(world). At the current implementation this statement throws an error. But why, world does provide a string/unicode interface (world.__str__ or str(world)), acutally.


The problem there is that world.Note is not a python function, it's a COM function and the python interpreter doesn't know that it's expecting a string, so passes the world object without casting it to a string. The error comes from the COM interface, not Python.

It wasen't realy meant as a question. It was more a ironic question, why the COM interface exspects strings, but doesn't accept -string like- object. That's absoltly not -pythonic- (to use this phrase again).

Quote:
Isthiriel wrote:
You mean like in my addxml.pys? :)
http://www.gammon.com.au/forum/?id=8398


Yes, something like that. A thin object wrapper, which uses the GetInfo methods with it's ugly numerical arguments to populte the attributes.

Quote:
Isthiriel wrote:
Of course, set_flags could probably be implemented better. (Pythonic set springs to mind.)

The flags are ok in my eyes. Sets would be an alternative, but I have never seen flags implemented as a set in Python. I don't know what the Python way to do something like that.

Quote:
Isthiriel wrote:
execfile() does achieve most of what import does, however IIRC, when it fails, instead of getting a nice ImportError with a stacktrace, the message is fairly meaningless :(

But "importing" with exec doesn't stick to the Zen of Python:
"Namespaces are one honking great idea -- let's do more of those!"
However, is there any advantage of using the '.pys' extensions? I would just use '.py'. Because exec imports have many disadvantages, like the broken error handling on import.
[Go to top] top

Posted by Aryzu   (12 posts)  [Biography] bio
Date Reply #9 on Thu 29 Jan 2009 11:51 PM (UTC)

Amended on Fri 30 Jan 2009 12:19 AM (UTC) by Aryzu

Message
I have written a interface class to write scripts in a more OOP way. The interface needs the globals dictionary of the script file in order to register global callbacks, which are defined by setting the descriptor 'extern'. You can have multiple script classes or objects. All the callback functions are all called sequentially. Here is a sample of a basic script file. What do you think so far?

import os, sys
cwd = os.path.split(world.GetAlphaOption('script_filename'))[0]
os.chdir(cwd)
if not cwd in sys.path:
    sys.path.append(cwd)
# ---------------------------------------------------------
# Pre-Amble above this line. DO NOT MODIFY
# ---------------------------------------------------------


import interface
from interface import extern

class MyScript(interface.Script):
    def __init__(self, *args, **kargs):
        interface.Script.__init__(self, *args, **kargs)
        self.world.Note('MyScript loaded')
    
    @extern
    def OnPrompt(self, name, line, wildcards):
        self.world.Note('MyScript.OnPrompt: %s, %s, %s' % 
                (name, line, wildcards))


class MySecondScript(interface.Script):
    def __init__(self, *args, **kargs):
        interface.Script.__init__(self, *args, **kargs)
        self.world.Note('MySecondScript loaded')
    
    @extern
    def OnPrompt(self, name, line, wildcards):
        self.world.Note('MyScript.OnPrompt: %s, %s, %s' % 
                (name, line, wildcards))


s1 = MyScript(globals())
s2 = MySecondScript(globals())



EDIT: You can get the source of interface.py from:
http://tinyurl.com/bjvjos
[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.


31,081 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]